2024/10/14

TopoJSON, GeoJSON

GeoJSON 是一種用 JSON 文件格式描述地圖的格式,2016 年 IETF 於 RFC 7946 規範了 GeoJSON 的規格。GeoJSON 的幾何物件有點:表示地理位置、線:表示街道公路邊界、多邊形:表示國家鄉鎮市界。

TopoJSON 是 GeoJSON 的擴充,TopoJSON 以一連串的點組合成的 Arcs 描述,line 與 polygon 都改用 arcs 描述,如果是邊界,在 TopoJSON 裡面的 arc 只會定義一次,這樣可有效減少文件的大小。

要將 TopoJSON 與 GeoJSON 文件互相轉換,可使用 node module

npm install topojson
npm install geojson

安裝後切換到 node_modules/topojson/node_modules/topojson-server/bin 這個目錄,可看到 geo2topo 指令

以下指令可將 GeoJSON 檔案轉換為 TopoJSON

./geo2topo towns-09007.geo.json > towns-09007.topo.json

切換到 node_modules/topojson/node_modules/topojson-client/bin 這個目錄,可看到 topo2geo 指令

這個指令可查詢 TopoJSON 裡面的地圖名稱

./topo2geo -l < towns-090007.topo.json
# towns-09007.geo

這邊會查詢到名稱為 towns-09007.geo

用以下指令將 TopoJSON 轉為 GeoJSON

./topo2geo towns-09007.geo=towns-090007-2.geo.json < towns-090007.topo.json

java jts library

以下節錄 GeoJSON 文件結構

{
    "type": "FeatureCollection",
    "features": [
        {
            "type": "Feature",
            "properties": {
                "id": "10005160",
                "name": "三灣鄉"
            },
            "geometry": {
                "type": "Polygon",
                "coordinates": [
                    [
                        [
                            120.97453105516638,
                            24.583295428280817
                        ],
                        [
                            120.96669830509721,
                            24.586708627549427
                        ],
                        ......
                    ]
                ]
            },
            ......
        }
    ]
}

這邊使用了兩個 library: jts, jackson,jackson 是處理 JSON 文件,jts 是處理向量圖形的 library

        <!-- https://github.com/locationtech/jts -->
        <!-- https://mvnrepository.com/artifact/org.locationtech.jts/jts-core -->
        <dependency>
            <groupId>org.locationtech.jts</groupId>
            <artifactId>jts-core</artifactId>
            <version>1.19.0</version>
        </dependency>
        <dependency>
            <groupId>org.locationtech.jts.io</groupId>
            <artifactId>jts-io-common</artifactId>
            <version>1.19.0</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.17.0</version>
        </dependency>

透過 Jackson,將 FeatureCollection 裡面的 features array 分開,每一個獨立去查詢,GPS 點跟 每一個 feature 的 Polygon// 測試每一個 point 跟 polygon 的關係。

相關的 methods 有這些:

  • 相等(Equals):幾何形狀拓撲上相等。

  • 脫節(Disjoint):幾何形狀沒有共有的點。

  • 相交(Intersects):幾何形狀至少有一個共有點(區別於脫節)

  • 接觸(Touches):幾何形狀有至少一個公共的邊界點,但是沒有內部點。

  • 交叉(Crosses):幾何形狀共享一些但不是所有的內部點。

  • 內含(Within):幾何形狀A的線都在幾何形狀B內部。

  • 包含(Contains):幾何形狀B的線都在幾何形狀A內部(區別於內含)

  • 重疊(Overlaps):幾何形狀共享一部分但不是所有的公共點,而且相交處有他們自己相同的區域。

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.locationtech.jts.geom.*;
import org.locationtech.jts.io.ParseException;
import org.locationtech.jts.io.geojson.GeoJsonReader;

import java.io.File;
import java.io.IOException;

public class PointInsidePolygon {

    public static void main(String[] args) throws IOException {
        // https://blog.csdn.net/qq_36427942/article/details/129123733
        // jackson lib to read json document
        ObjectMapper mapper = new ObjectMapper();
        ObjectNode geoJsonObject = (ObjectNode) mapper.readTree(new File("towns-10005.geo.json"));

//        System.out.println("geoJsonObject.toString()="+geoJsonObject.toString());

        // 透過 Jackson,將 FeatureCollection 裡面的 features array 分開
        // 每一個獨立去查詢,GPS 點跟 每一個 feature 的 Polygon
        // 測試每一個 point 跟 polygon 的關係
        Coordinate GPSPint = new Coordinate(120.97453105516638,24.583295428280817);
        JsonNode node2 = geoJsonObject.get("features");
//        System.out.println("node2="+node2);
        for(JsonNode node3: node2) {
//            System.out.println("node3="+node3);
            JsonNodeType node3Type = node3.getNodeType();
            JsonNode node3PropertiesNode = node3.get("properties");
            JsonNode node3PropertiesId = node3PropertiesNode.get("id");
            JsonNode node3PropertiesName = node3PropertiesNode.get("name");

            System.out.println("");
            System.out.println("node3PropertiesId="+node3PropertiesId+", node3PropertiesName="+node3PropertiesName);

            GeoJsonReader reader = new GeoJsonReader();
            Geometry geometry = null;
            try {
                geometry = reader.read(node3.toString());
            } catch (ParseException e) {
                throw new RuntimeException(e);
            }

            String geometryType = geometry.getGeometryType();
            // geometryType=GeometryCollection
            System.out.println("geometryType="+geometryType+", length="+ geometry.getLength());

//        Coordinate[] cors = geometry.getCoordinates();
//        System.out.println("cors length="+cors.length);
//        for(Coordinate c: cors) {
//            System.out.println("c ="+c.toString() );
//        }
            // get ExteriorRing
            if (geometry instanceof Polygon) {
                geometry = ((Polygon) geometry).getExteriorRing();
            } else {
                System.err.println("Invalid Polygon");
                return;
            }

            // JTS Geometry
            GeometryFactory geometryFactory = new GeometryFactory();
            Coordinate[] coordinates = geometry.getCoordinates();
            Coordinate[] jtsCoordinates = new Coordinate[coordinates.length];
            for (int i = 0; i < coordinates.length; i++) {
                jtsCoordinates[i] = new Coordinate(coordinates[i].x, coordinates[i].y);
            }
            Polygon polygon = geometryFactory.createPolygon(jtsCoordinates);

            // GPS Point
            Point gpsPoint = geometryFactory.createPoint(GPSPint);

//            相等(Equals):幾何形狀拓撲上相等。
//            脫節(Disjoint):幾何形狀沒有共有的點。
//            相交(Intersects):幾何形狀至少有一個共有點(區別於脫節)
//            接觸(Touches):幾何形狀有至少一個公共的邊界點,但是沒有內部點。
//            交叉(Crosses):幾何形狀共享一些但不是所有的內部點。
//            內含(Within):幾何形狀A的線都在幾何形狀B內部。
//            包含(Contains):幾何形狀B的線都在幾何形狀A內部(區別於內含)
//            重疊(Overlaps):幾何形狀共享一部分但不是所有的公共點,而且相交處有他們自己相同的區域。
            boolean isInside = polygon.contains(gpsPoint);
            boolean isWithin = polygon.within(gpsPoint);
            boolean intersects = polygon.intersects(gpsPoint);
            boolean overlaps = polygon.overlaps(gpsPoint);
            boolean crosses = polygon.crosses(gpsPoint);
            boolean touches = polygon.touches(gpsPoint);
            boolean disjoint = polygon.disjoint(gpsPoint);

            System.out.println("gps "+gpsPoint);
            System.out.println(" contains=" + isInside+", within=" + isWithin+", intersects="+intersects+". overlaps="+overlaps+", crosses="+crosses+", touches="+ touches+", disjoint="+disjoint);
        }
    }
}

執行結果如下:

node3PropertiesId="10005160", node3PropertiesName="三灣鄉"
geometryType=Polygon, length=0.4571892567423512
gps POINT (120.97453105516638 24.583295428280817)
 contains=false, within=false, intersects=true. overlaps=false, crosses=false, touches=true, disjoint=false

node3PropertiesId="10005110", node3PropertiesName="南庄鄉"
geometryType=Polygon, length=0.6073270143853203
gps POINT (120.97453105516638 24.583295428280817)
 contains=false, within=false, intersects=true. overlaps=false, crosses=false, touches=true, disjoint=false

node3PropertiesId="10005010", node3PropertiesName="苗栗市"
geometryType=Polygon, length=0.26286982385854196
gps POINT (120.97453105516638 24.583295428280817)
 contains=false, within=false, intersects=false. overlaps=false, crosses=false, touches=false, disjoint=true

References

D3.js應用

「GIS教程」将GeoJSON转换成TopoJSON的方法 | 麻辣GIS

GeoJSON - Wikipedia

沒有留言:

張貼留言