1 package org
.argeo
.app
.geo
;
3 import org
.locationtech
.jts
.geom
.Coordinate
;
4 import org
.locationtech
.jts
.geom
.Envelope
;
5 import org
.locationtech
.jts
.geom
.Geometry
;
6 import org
.locationtech
.jts
.geom
.GeometryCollection
;
7 import org
.locationtech
.jts
.geom
.LineString
;
8 import org
.locationtech
.jts
.geom
.LinearRing
;
9 import org
.locationtech
.jts
.geom
.MultiPoint
;
10 import org
.locationtech
.jts
.geom
.Point
;
11 import org
.locationtech
.jts
.geom
.Polygon
;
13 import jakarta
.json
.JsonArray
;
14 import jakarta
.json
.JsonObject
;
15 import jakarta
.json
.stream
.JsonGenerator
;
20 * @see https://datatracker.ietf.org/doc/html/rfc7946
22 public class GeoJson
{
23 public final static String POINT_TYPE
= "Point";
24 public final static String MULTI_POINT_TYPE
= "MultiPoint";
25 public final static String LINE_STRING_TYPE
= "LineString";
26 public final static String POLYGON_TYPE
= "Polygon";
27 public final static String GEOMETRY_COLLECTION_TYPE
= "GeometryCollection";
29 public final static String TYPE
= "type";
30 public final static String GEOMETRY
= "geometry";
31 public final static String GEOMETRIES
= "geometries";
32 public final static String COORDINATES
= "coordinates";
33 public final static String BBOX
= "bbox";
34 public final static String PROPERTIES
= "properties";
39 /** Writes a {@link Geometry} as GeoJSON. */
40 public static void writeGeometry(JsonGenerator g
, Geometry geometry
) {
41 if (geometry
instanceof Point point
) {
42 g
.write(TYPE
, POINT_TYPE
);
43 g
.writeStartArray(COORDINATES
);
44 writeCoordinate(g
, point
.getCoordinate());
45 g
.writeEnd();// coordinates array
46 } else if (geometry
instanceof MultiPoint multiPoint
) {
47 g
.write(TYPE
, MULTI_POINT_TYPE
);
48 g
.writeStartArray(COORDINATES
);
49 writeCoordinates(g
, multiPoint
.getCoordinates());
50 g
.writeEnd();// coordinates array
51 } else if (geometry
instanceof LineString lineString
) {
52 g
.write(TYPE
, LINE_STRING_TYPE
);
53 g
.writeStartArray(COORDINATES
);
54 writeCoordinates(g
, lineString
.getCoordinates());
55 g
.writeEnd();// coordinates array
56 } else if (geometry
instanceof Polygon polygon
) {
57 g
.write(TYPE
, POLYGON_TYPE
);
58 g
.writeStartArray(COORDINATES
);
59 LinearRing exteriorRing
= polygon
.getExteriorRing();
61 writeCoordinates(g
, exteriorRing
.getCoordinates());
63 for (int i
= 0; i
< polygon
.getNumInteriorRing(); i
++) {
64 LinearRing interiorRing
= polygon
.getInteriorRingN(i
);
65 // TODO verify that holes are clockwise
67 writeCoordinates(g
, interiorRing
.getCoordinates());
70 g
.writeEnd();// coordinates array
71 } else if (geometry
instanceof GeometryCollection geometryCollection
) {// must be last
72 g
.write(TYPE
, GEOMETRY_COLLECTION_TYPE
);
73 g
.writeStartArray(GEOMETRIES
);
74 for (int i
= 0; i
< geometryCollection
.getNumGeometries(); i
++) {
76 writeGeometry(g
, geometryCollection
.getGeometryN(i
));
77 g
.writeEnd();// geometry object
79 g
.writeEnd();// geometries array
81 throw new IllegalArgumentException(geometry
.getClass() + " is not supported.");
85 /** Writes a sequence of coordinates [[lat,lon],[lat,lon]] */
86 public static void writeCoordinates(JsonGenerator g
, Coordinate
[] coordinates
) {
87 for (Coordinate coordinate
: coordinates
) {
89 writeCoordinate(g
, coordinate
);
94 /** Writes a pair of coordinates [lat,lon]. */
95 public static void writeCoordinate(JsonGenerator g
, Coordinate coordinate
) {
96 g
.write(coordinate
.getX());
97 g
.write(coordinate
.getY());
98 double z
= coordinate
.getZ();
99 if (!Double
.isNaN(z
)) {
105 * Writes the {@link Envelope} of a {@link Geometry} as a bbox GeoJSON object.
107 public static void writeBBox(JsonGenerator g
, Geometry geometry
) {
108 g
.writeStartArray(BBOX
);
109 Envelope envelope
= geometry
.getEnvelopeInternal();
110 g
.write(envelope
.getMinX());
111 g
.write(envelope
.getMinY());
112 g
.write(envelope
.getMaxX());
113 g
.write(envelope
.getMaxY());
120 /** Reads a geometry from the geometry object of a GEoJSON feature. */
121 @SuppressWarnings("unchecked")
122 public static <T
extends Geometry
> T
readGeometry(JsonObject geom
, Class
<T
> clss
) {
123 String type
= geom
.getString(TYPE
);
124 JsonArray coordinates
= geom
.getJsonArray(COORDINATES
);
125 Geometry res
= switch (type
) {
127 Coordinate coord
= readCoordinate(coordinates
);
128 yield JTS
.GEOMETRY_FACTORY_WGS84
.createPoint(coord
);
130 case LINE_STRING_TYPE
: {
131 Coordinate
[] coords
= readCoordinates(coordinates
);
132 yield JTS
.GEOMETRY_FACTORY_WGS84
.createLineString(coords
);
135 assert coordinates
.size() > 0;
136 LinearRing exterior
= JTS
.GEOMETRY_FACTORY_WGS84
137 .createLinearRing(readCoordinates(coordinates
.getJsonArray(0)));
138 LinearRing
[] holes
= new LinearRing
[coordinates
.size() - 1];
139 for (int i
= 0; i
< coordinates
.size() - 1; i
++) {
140 holes
[i
] = JTS
.GEOMETRY_FACTORY_WGS84
141 .createLinearRing(readCoordinates(coordinates
.getJsonArray(i
+ 1)));
143 yield JTS
.GEOMETRY_FACTORY_WGS84
.createPolygon(exterior
, holes
);
146 throw new IllegalArgumentException("Unexpected value: " + type
);
152 /** Reads a coordinate sequence [[lat,lon],[lat,lon]]. */
153 public static Coordinate
readCoordinate(JsonArray arr
) {
154 assert arr
.size() >= 2;
155 return new Coordinate(arr
.getJsonNumber(0).doubleValue(), arr
.getJsonNumber(1).doubleValue());
158 /** Reads a coordinate pair [lat,lon]. */
159 public static Coordinate
[] readCoordinates(JsonArray arr
) {
160 Coordinate
[] coords
= new Coordinate
[arr
.size()];
161 for (int i
= 0; i
< arr
.size(); i
++)
162 coords
[i
] = readCoordinate(arr
.getJsonArray(i
));