diff --git a/meteoinfo-common/src/main/java/org/meteoinfo/common/PointD.java b/meteoinfo-common/src/main/java/org/meteoinfo/common/PointD.java index 3c23ebaf..e5866ebc 100644 --- a/meteoinfo-common/src/main/java/org/meteoinfo/common/PointD.java +++ b/meteoinfo-common/src/main/java/org/meteoinfo/common/PointD.java @@ -33,7 +33,8 @@ public class PointD implements Cloneable{ } /** - * Contructor + * Constructor + * * @param x * @param y */ @@ -46,6 +47,22 @@ public class PointD implements Cloneable{ // // + /** + * To double array + * @return Double array + */ + public double[] toArray() { + return new double[]{X, Y}; + } + + /** + * To float array + * @return Float array + */ + public float[] toFloatArray() { + return new float[]{(float) X, (float) Y}; + } + /** * Equals of two pointDs * @param p PointD diff --git a/meteoinfo-common/src/main/java/org/meteoinfo/common/colors/ColorUtil.java b/meteoinfo-common/src/main/java/org/meteoinfo/common/colors/ColorUtil.java index 9baf2e1a..956034a1 100644 --- a/meteoinfo-common/src/main/java/org/meteoinfo/common/colors/ColorUtil.java +++ b/meteoinfo-common/src/main/java/org/meteoinfo/common/colors/ColorUtil.java @@ -137,6 +137,15 @@ public class ColorUtil { // // + /** + * Convert color r,g,b to hex string + * @param color The color + * @return Hex string + */ + public static String toHex(Color color) { + return String.format("#%02X%02X%02X", color.getRed(), color.getGreen(), color.getBlue()); + } + /** * Convert a color to hex string * diff --git a/meteoinfo-common/src/main/java/org/meteoinfo/common/util/GlobalUtil.java b/meteoinfo-common/src/main/java/org/meteoinfo/common/util/GlobalUtil.java index c685b7dd..bd43931c 100644 --- a/meteoinfo-common/src/main/java/org/meteoinfo/common/util/GlobalUtil.java +++ b/meteoinfo-common/src/main/java/org/meteoinfo/common/util/GlobalUtil.java @@ -67,7 +67,7 @@ import java.util.zip.ZipInputStream; public static String getVersion(){ String version = GlobalUtil.class.getPackage().getImplementationVersion(); if (version == null || version.equals("")) { - version = "3.7.8"; + version = "3.7.9"; } return version; } diff --git a/meteoinfo-geo/src/main/java/org/meteoinfo/geo/io/GeoJSONReader.java b/meteoinfo-geo/src/main/java/org/meteoinfo/geo/io/GeoJSONReader.java new file mode 100644 index 00000000..098b897e --- /dev/null +++ b/meteoinfo-geo/src/main/java/org/meteoinfo/geo/io/GeoJSONReader.java @@ -0,0 +1,77 @@ +package org.meteoinfo.geo.io; + +import org.meteoinfo.common.colors.ColorUtil; +import org.meteoinfo.geo.layer.VectorLayer; +import org.meteoinfo.geometry.io.geojson.*; +import org.meteoinfo.geometry.legend.ColorBreak; +import org.meteoinfo.geometry.legend.LegendScheme; +import org.meteoinfo.geometry.legend.LegendType; +import org.meteoinfo.geometry.legend.PolygonBreak; +import org.meteoinfo.geometry.shape.Shape; +import org.meteoinfo.geometry.shape.ShapeTypes; +import org.meteoinfo.ndarray.DataType; + +import java.awt.*; +import java.util.Map; + +public class GeoJSONReader { + + /** + * Create a VectorLayer from GeoJSON feature collection + * @param features The feature collection + * @return VectorLayer object + */ + public static VectorLayer read(FeatureCollection features) { + Shape shape = GeoJSONUtil.toShape(features.getFeature(0).getGeometry()); + VectorLayer layer = new VectorLayer(shape.getShapeType()); + String fieldName = "title"; + layer.editAddField(fieldName, DataType.STRING); + LegendScheme ls = new LegendScheme(shape.getShapeType()); + ls.setLegendType(LegendType.UNIQUE_VALUE); + ls.setFieldName(fieldName); + for (int i = 0; i < features.getNumFeatures(); i++) { + Feature feature = features.getFeature(i); + Map properties = feature.getProperties(); + String titleValue = (String) properties.get("title"); + PolygonBreak cb = new PolygonBreak(); + cb.setStartValue(titleValue); + cb.setCaption(titleValue); + Color color = ColorUtil.parseToColor((String) properties.get("fill")); + float alpha = Float.parseFloat(properties.get("fill-opacity").toString()); + color = ColorUtil.getColor(color, alpha); + cb.setColor(color); + color = ColorUtil.parseToColor((String) properties.get("stroke")); + alpha = Float.parseFloat(properties.get("stroke-opacity").toString()); + color = ColorUtil.getColor(color, alpha); + cb.setOutlineColor(color); + float lineWidth = Float.parseFloat(properties.get("stroke-width").toString()); + cb.setOutlineSize(lineWidth); + ls.addLegendBreak(cb); + Geometry geometry = feature.getGeometry(); + if (geometry != null) { + try { + int idx = layer.getShapeNum(); + layer.editInsertShape(GeoJSONUtil.toShape(geometry), idx); + layer.editCellValue(fieldName, idx, titleValue); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + layer.setLegendScheme(ls); + + return layer; + } + + /** + * Create a VectorLayer from GeoJSON string + * @param json The GeoJSON string + * @return VectorLayer object + */ + public static VectorLayer read(String json) { + FeatureCollection features = (FeatureCollection) GeoJSONFactory.create(json); + + return read(features); + } + +} diff --git a/meteoinfo-geo/src/main/java/org/meteoinfo/geo/io/GeoJSONWriter.java b/meteoinfo-geo/src/main/java/org/meteoinfo/geo/io/GeoJSONWriter.java new file mode 100644 index 00000000..92fb2c49 --- /dev/null +++ b/meteoinfo-geo/src/main/java/org/meteoinfo/geo/io/GeoJSONWriter.java @@ -0,0 +1,63 @@ +package org.meteoinfo.geo.io; + +import org.meteoinfo.common.colors.ColorUtil; +import org.meteoinfo.geo.layer.VectorLayer; +import org.meteoinfo.geometry.io.geojson.Feature; +import org.meteoinfo.geometry.io.geojson.FeatureCollection; +import org.meteoinfo.geometry.io.geojson.GeoJSONUtil; +import org.meteoinfo.geometry.io.geojson.Geometry; +import org.meteoinfo.geometry.legend.*; +import org.meteoinfo.geometry.shape.Shape; + +import java.util.HashMap; +import java.util.Map; + +public class GeoJSONWriter { + + /** + * Write a vector layer to GeoJSON feature collection + * + * @param layer The vector layer + * @return GeoJSON feature collection + */ + public static FeatureCollection write(VectorLayer layer) { + Feature[] features = new Feature[layer.getShapeNum()]; + LegendScheme ls = layer.getLegendScheme(); + for (int i = 0; i < layer.getShapeNum(); i++) { + Shape shape = layer.getShape(i); + Geometry geometry = GeoJSONUtil.fromShape(shape); + Map properties = new HashMap<>(); + ColorBreak cb = ls.getLegendBreak(shape.getLegendIndex()); + switch (cb.getBreakType()) { + case POINT_BREAK: + PointBreak pointBreak = (PointBreak) cb; + properties.put("marker-size", "medium"); + properties.put("marker-color", ColorUtil.toHex(pointBreak.getColor())); + break; + case POLYLINE_BREAK: + PolylineBreak polylineBreak = (PolylineBreak) cb; + properties.put("stroke", ColorUtil.toHex(polylineBreak.getColor())); + properties.put("stroke-opacity", polylineBreak.getColor().getAlpha() / 255.f); + properties.put("stroke-width", polylineBreak.getWidth()); + break; + case POLYGON_BREAK: + PolygonBreak polygonBreak = (PolygonBreak) cb; + properties.put("fill", ColorUtil.toHex(cb.getColor())); + properties.put("fill-opacity", cb.getColor().getAlpha() / 255.f); + if (polygonBreak.isDrawOutline()) { + properties.put("stroke", ColorUtil.toHex(polygonBreak.getOutlineColor())); + properties.put("stroke-opacity", polygonBreak.getOutlineColor().getAlpha() / 255.f); + properties.put("stroke-width", polygonBreak.getOutlineSize()); + } else { + properties.put("stroke-opacity", 0); + } + break; + } + properties.put("title", cb.getCaption()); + features[i] = new Feature(geometry, properties); + } + + return new FeatureCollection(features); + } + +} diff --git a/meteoinfo-geo/src/main/java/org/meteoinfo/geo/layer/VectorLayer.java b/meteoinfo-geo/src/main/java/org/meteoinfo/geo/layer/VectorLayer.java index e6fb13a9..01940014 100644 --- a/meteoinfo-geo/src/main/java/org/meteoinfo/geo/layer/VectorLayer.java +++ b/meteoinfo-geo/src/main/java/org/meteoinfo/geo/layer/VectorLayer.java @@ -1412,6 +1412,17 @@ public class VectorLayer extends MapLayer { } } + /** + * Edit: Remove a shape by index + * @param idx The index + */ + public void editRemoveShape(int idx) { + if (idx >= 0 && idx < this.getShapeNum() - 1) { + this._shapeList.remove(idx); + this._attributeTable.getTable().removeRow(idx); + } + } + private void updateLayerExtent(Shape aShape) { if (this.getShapeNum() == 1) { this.setExtent((Extent) aShape.getExtent().clone()); diff --git a/meteoinfo-geometry/pom.xml b/meteoinfo-geometry/pom.xml index dbce8ef6..c8945ff0 100644 --- a/meteoinfo-geometry/pom.xml +++ b/meteoinfo-geometry/pom.xml @@ -37,6 +37,16 @@ jts-core 1.19.0 + + com.fasterxml.jackson.core + jackson-databind + 2.13.4.2 + + + com.fasterxml.jackson.core + jackson-annotations + 2.13.4 + diff --git a/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/Feature.java b/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/Feature.java new file mode 100644 index 00000000..e62daed8 --- /dev/null +++ b/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/Feature.java @@ -0,0 +1,46 @@ +package org.meteoinfo.geometry.io.geojson; + +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +@JsonPropertyOrder({"type", "id", "geometry", "properties"}) +public class Feature extends GeoJSON { + @JsonInclude(Include.NON_EMPTY) + private final Object id; + private final Geometry geometry; + private final Map properties; + + public Feature( + @JsonProperty("geometry") Geometry geometry, + @JsonProperty("properties") Map properties) { + this(null, geometry, properties); + } + + @JsonCreator + public Feature( + @JsonProperty("id") Object id, + @JsonProperty("geometry") Geometry geometry, + @JsonProperty("properties") Map properties) { + super(); + this.id = id; + this.geometry = geometry; + this.properties = properties; + } + + public Object getId() { + return id; + } + + public Geometry getGeometry() { + return geometry; + } + + public Map getProperties() { + return properties; + } +} diff --git a/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/FeatureCollection.java b/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/FeatureCollection.java new file mode 100644 index 00000000..e2c16aba --- /dev/null +++ b/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/FeatureCollection.java @@ -0,0 +1,41 @@ +package org.meteoinfo.geometry.io.geojson; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonPropertyOrder({"type", "features"}) +public class FeatureCollection extends GeoJSON { + private final Feature[] features; + + @JsonCreator + public FeatureCollection(@JsonProperty("features") Feature[] features) { + super(); + this.features = features; + } + + /** + * Get features + * @return Features + */ + public Feature[] getFeatures() { + return features; + } + + /** + * Get number of features + * @return Number of features + */ + public int getNumFeatures() { + return features.length; + } + + /** + * Get a feature by index + * @param index The index + * @return The feature + */ + public Feature getFeature(int index) { + return features[index]; + } +} diff --git a/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/GeoJSON.java b/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/GeoJSON.java new file mode 100644 index 00000000..25dae325 --- /dev/null +++ b/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/GeoJSON.java @@ -0,0 +1,58 @@ +package org.meteoinfo.geometry.io.geojson; + +import java.io.IOException; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.core.JsonGenerationException; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.EXISTING_PROPERTY, + property = "type" +) +@JsonSubTypes( { + @JsonSubTypes.Type(value=Point.class, name="Point" ), + @JsonSubTypes.Type(value=LineString.class, name="LineString" ), + @JsonSubTypes.Type(value=Polygon.class, name="Polygon" ), + @JsonSubTypes.Type(value=MultiPoint.class, name="MultiPoint" ), + @JsonSubTypes.Type(value=MultiLineString.class, name="MultiLineString" ), + @JsonSubTypes.Type(value=MultiPolygon.class, name="MultiPolygon" ), + @JsonSubTypes.Type(value=Feature.class, name="Feature" ), + @JsonSubTypes.Type(value=FeatureCollection.class, name="FeatureCollection" ), + @JsonSubTypes.Type(value=GeometryCollection.class, name="GeometryCollection" ) +}) +public abstract class GeoJSON { + private static final ObjectMapper mapper = new ObjectMapper(); + + @JsonProperty("type") + private String type; + + @JsonCreator + public GeoJSON() { + setType(getClass().getSimpleName()); + } + + public String toString() { + try { + return mapper.writeValueAsString(this); + } catch (JsonGenerationException e) { + return "Unhandled exception occured when serializing this instance"; + } catch (JsonMappingException e) { + return "Unhandled exception occured when serializing this instance"; + } catch (IOException e) { + return "Unhandled exception occured when serializing this instance"; + } + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } +} diff --git a/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/GeoJSONFactory.java b/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/GeoJSONFactory.java new file mode 100644 index 00000000..ed42262b --- /dev/null +++ b/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/GeoJSONFactory.java @@ -0,0 +1,65 @@ +package org.meteoinfo.geometry.io.geojson; + +import java.io.IOException; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.ArrayList; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.type.MapType; + +public class GeoJSONFactory { + private static final ObjectMapper mapper = new ObjectMapper(); + + public static GeoJSON create(String json) { + try { + JsonNode node = mapper.readTree(json); + String type = node.get("type").asText(); + if (type.equals("FeatureCollection")) + return readFeatureCollection(node); + else if (type.equals("Feature")) + return readFeature(node); + else + return readGeometry(node, type); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static FeatureCollection readFeatureCollection(JsonNode node) + throws JsonParseException, JsonMappingException, IOException, ClassNotFoundException { + Iterator it = node.get("features").iterator(); + List features = new ArrayList<>(); + while (it.hasNext()) + features.add(readFeature(it.next())); + return new FeatureCollection(features.toArray(new Feature[features.size()])); + } + + private static Feature readFeature(JsonNode node) + throws JsonParseException, JsonMappingException, IOException, ClassNotFoundException { + JsonNode geometryNode = node.get("geometry"); + MapType javaType = mapper.getTypeFactory().constructMapType(Map.class, String.class, Object.class); + JsonNode id = node.get("id"); + Map properties = mapper.readValue(node.get("properties").traverse(), javaType); + Geometry geometry = readGeometry(geometryNode); + return new Feature(id, geometry, properties); + } + + private static Geometry readGeometry(JsonNode node) + throws JsonParseException, JsonMappingException, IOException, ClassNotFoundException { + if (node != null && !node.isNull()) + return readGeometry(node, node.get("type").asText()); + else + return null; + } + + private static Geometry readGeometry(JsonNode node, String type) + throws JsonParseException, JsonMappingException, IOException, ClassNotFoundException { + return (Geometry) mapper.readValue(node.traverse(), Class.forName("org.meteoinfo.geometry.io.geojson." + type)); + } + +} diff --git a/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/GeoJSONUtil.java b/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/GeoJSONUtil.java new file mode 100644 index 00000000..cfb4fb00 --- /dev/null +++ b/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/GeoJSONUtil.java @@ -0,0 +1,341 @@ +package org.meteoinfo.geometry.io.geojson; + +import org.meteoinfo.common.PointD; +import org.meteoinfo.geometry.shape.*; +import java.util.ArrayList; +import java.util.List; + +public class GeoJSONUtil { + + /** + * Convert GeoJSON geometry to shape + * + * @param geometry The geometry + * @return Shape + */ + public static Shape toShape(Geometry geometry) { + if (geometry instanceof Point) { + return toShape((Point) geometry); + } else if (geometry instanceof LineString) { + return toShape((LineString) geometry); + } else if (geometry instanceof MultiLineString) { + return toShape((MultiLineString) geometry); + } else if (geometry instanceof Polygon) { + return toShape((Polygon) geometry); + } else if (geometry instanceof MultiPolygon) { + return toShape((MultiPolygon) geometry); + } else { + throw new UnsupportedOperationException(); + } + } + + /** + * Convert shape to GeoJSON geometry + * + * @param shape The shape + * @return Geometry + */ + public static Geometry fromShape(Shape shape) { + if (shape instanceof PointShape) { + return fromShape((PointShape) shape); + } else if (shape instanceof PolylineShape) { + return fromShape((PolylineShape) shape); + } else if (shape instanceof PolygonShape) { + return fromShape((PolygonShape) shape); + } else { + throw new UnsupportedOperationException(); + } + } + + /** + * Convert PointShape to GeoJSON Point + * + * @param pointShape The PointShape object + * @return GeoJSON Point object + */ + public static Point fromShape(PointShape pointShape) { + PointD p = pointShape.getPoint(); + + return new Point(p.toArray()); + } + + /** + * Convert GeoJSON Point to PointShape + * + * @param point The GeoJSON Point object + * @return PointShape object + */ + public static PointShape toShape(Point point) { + double[] coordinates = point.getCoordinates(); + if (coordinates.length == 2) { + return new PointShape(new PointD(coordinates[0], coordinates[1])); + } else { + return new PointZShape(new PointZ(coordinates[0], coordinates[1], coordinates[2])); + } + } + + /** + * Convert PolylineShape to GeoJSON LineString or MultiLineString + * + * @param polylineShape The PolylineShape object + * @return GeoJSON LineString or MultiLineString object + */ + public static Geometry fromShape(PolylineShape polylineShape) { + if (polylineShape.isMultiLine()) { + int lineNum = polylineShape.getPartNum(); + double[][][] coordinates = new double[lineNum][][]; + int cNum = polylineShape.getShapeType() == ShapeTypes.POLYLINE ? 2 : 3; + for (int j = 0; j < lineNum; j++) { + Polyline polyline = polylineShape.getPolylines().get(j); + int pNum = polyline.getPointList().size(); + double[][] matrix = new double[pNum][cNum]; + for (int i = 0; i < pNum; i++) { + matrix[i] = polyline.getPointList().get(i).toArray(); + } + coordinates[j] = matrix; + } + + return new MultiLineString(coordinates); + } else { + int cNum = polylineShape.getShapeType() == ShapeTypes.POLYLINE ? 2 : 3; + double[][] coordinates = new double[polylineShape.getPointNum()][cNum]; + for (int i = 0; i < polylineShape.getPointNum(); i++) { + coordinates[i] = polylineShape.getPoints().get(i).toArray(); + } + + return new LineString(coordinates); + } + } + + /** + * Convert GeoJSON LineString to PolylineShape + * + * @param lineString The GeoJSON LineString object + * @return PolylineShape object + */ + public static PolylineShape toShape(LineString lineString) { + double[][] coordinates = lineString.getCoordinates(); + int pNum = coordinates.length; + if (coordinates[0].length == 2) { + List points = new ArrayList<>(); + for (int i = 0; i < pNum; i++) { + points.add(new PointD(coordinates[i][0], coordinates[i][1])); + } + + return new PolylineShape(points); + } else { + List points = new ArrayList<>(); + for (int i = 0; i < pNum; i++) { + points.add(new PointZ(coordinates[i][0], coordinates[i][1], coordinates[i][2])); + } + + return new PolylineZShape(points); + } + } + + /** + * Convert GeoJSON LineString to PolylineShape + * + * @param lineString The GeoJSON LineString object + * @return PolylineShape object + */ + public static PolylineShape toShape(MultiLineString lineString) { + double[][][] coordinates = lineString.getCoordinates(); + int lineNum = coordinates.length; + int cNum = coordinates[0][0].length; + if (cNum == 2) { + PolylineShape polylineShape = new PolylineShape(); + polylineShape.setPartNum(lineNum); + polylineShape.parts = new int[lineNum]; + List points = new ArrayList<>(); + for (int j = 0; j < lineNum; j++) { + int pNum = coordinates[j].length; + polylineShape.parts[j] = pNum; + for (int i = 0; i < pNum; i++) { + points.add(new PointD(coordinates[j][i][0], coordinates[j][i][1])); + } + } + polylineShape.setPoints(points); + + return polylineShape; + } else { + PolylineZShape polylineZShape = new PolylineZShape(); + polylineZShape.setPartNum(lineNum); + polylineZShape.parts = new int[lineNum]; + List points = new ArrayList<>(); + for (int j = 0; j < lineNum; j++) { + int pNum = coordinates[j].length; + polylineZShape.parts[j] = pNum; + for (int i = 0; i < pNum; i++) { + points.add(new PointZ(coordinates[j][i][0], coordinates[j][i][1], coordinates[j][i][2])); + } + } + polylineZShape.setPoints(points); + + return polylineZShape; + } + } + + /** + * Convert PolygonShape to GeoJSON Polygon or MultiPolygon + * + * @param polygonShape The PolygonShape object + * @return GeoJSON Polygon or MultiPolygon object + */ + public static Geometry fromShape(PolygonShape polygonShape) { + int cNum = polygonShape.getShapeType() == ShapeTypes.POLYGON ? 2 : 3; + if (polygonShape.isMultiPolygon()) { + int polygonNum = polygonShape.getPolygons().size(); + double[][][][] coordinates = new double[polygonNum][][][]; + for (int k = 0; k < polygonNum; k++) { + org.meteoinfo.geometry.shape.Polygon sPolygon = polygonShape.getPolygon(k); + int ringNumber = sPolygon.getRingNumber(); + double[][][] a3d = new double[ringNumber][][]; + int pNum = sPolygon.getOutLine().size(); + double[][] matrix = new double[pNum][cNum]; + for (int i = 0; i < pNum; i++) { + matrix[i] = sPolygon.getOutLine().get(i).toArray(); + } + a3d[0] = matrix; + if (sPolygon.hasHole()) { + for (int j = 0; j < sPolygon.getHoleLineNumber(); j++) { + pNum = sPolygon.getHoleLine(j).size(); + matrix = new double[pNum][cNum]; + for (int i = 0; i < sPolygon.getHoleLine(j).size(); i++) { + matrix[i] = sPolygon.getHoleLine(j).get(i).toArray(); + } + a3d[j + 1] = matrix; + } + } + coordinates[k] = a3d; + } + + return new MultiPolygon(coordinates); + } else { + org.meteoinfo.geometry.shape.Polygon sPolygon = polygonShape.getPolygon(0); + int ringNum = sPolygon.getRingNumber(); + double[][][] coordinates = new double[ringNum][][]; + int pNum = sPolygon.getOutLine().size(); + double[][] matrix = new double[pNum][cNum]; + for (int i = 0; i < pNum; i++) { + matrix[i] = sPolygon.getOutLine().get(i).toArray(); + } + coordinates[0] = matrix; + if (sPolygon.hasHole()) { + for (int j = 0; j < sPolygon.getHoleLineNumber(); j++) { + pNum = sPolygon.getHoleLine(j).size(); + matrix = new double[pNum][cNum]; + for (int i = 0; i < sPolygon.getHoleLine(j).size(); i++) { + matrix[i] = sPolygon.getHoleLine(j).get(i).toArray(); + } + coordinates[j + 1] = matrix; + } + } + + return new Polygon(coordinates); + } + } + + /** + * Convert GeoJSON Polygon to PolygonShape + * + * @param polygon The GeoJSON Polygon object + * @return PolygonShape object + */ + public static PolygonShape toShape(Polygon polygon) { + double[][][] coordinates = polygon.getCoordinates(); + int ringNum = coordinates.length; + int cNum = coordinates[0][0].length; + if (cNum == 2) { + PolygonShape polygonShape = new PolygonShape(); + polygonShape.setPartNum(ringNum); + polygonShape.parts = new int[ringNum]; + List points = new ArrayList<>(); + for (int j = 0; j < ringNum; j++) { + int pNum = coordinates[j].length; + polygonShape.parts[j] = pNum; + for (int i = 0; i < pNum; i++) { + points.add(new PointD(coordinates[j][i][0], coordinates[j][i][1])); + } + } + polygonShape.setPoints(points); + + return polygonShape; + } else { + PolygonZShape polygonZShape = new PolygonZShape(); + polygonZShape.setPartNum(ringNum); + polygonZShape.parts = new int[ringNum]; + List points = new ArrayList<>(); + for (int j = 0; j < ringNum; j++) { + int pNum = coordinates[j].length; + polygonZShape.parts[j] = pNum; + for (int i = 0; i < pNum; i++) { + points.add(new PointZ(coordinates[j][i][0], coordinates[j][i][1], coordinates[j][i][2])); + } + } + polygonZShape.setPoints(points); + + return polygonZShape; + } + } + + /** + * Convert GeoJSON MultiPolygon to PolygonShape + * + * @param multiPolygon The GeoJSON MultiPolygon object + * @return PolygonShape object + */ + public static PolygonShape toShape(MultiPolygon multiPolygon) { + double[][][][] coordinates = multiPolygon.getCoordinates(); + int polygonNum = coordinates.length; + int cNum = coordinates[0][0][0].length; + if (cNum == 2) { + List polygons = new ArrayList<>(); + for (int k = 0; k < polygonNum; k++) { + int ringNum = coordinates[k].length; + org.meteoinfo.geometry.shape.Polygon polygon = new org.meteoinfo.geometry.shape.Polygon(); + for (int j = 0; j < ringNum; j++) { + List points = new ArrayList<>(); + int pNum = coordinates[k][j].length; + for (int i = 0; i < pNum; i++) { + points.add(new PointD(coordinates[k][j][i][0], coordinates[k][j][i][1])); + } + if (j == 0) { + polygon.setOutLine(points); + } else { + polygon.addHole(points); + } + } + polygons.add(polygon); + } + PolygonShape polygonShape = new PolygonShape(); + polygonShape.setPolygons(polygons); + + return polygonShape; + } else { + List polygons = new ArrayList<>(); + for (int k = 0; k < polygonNum; k++) { + int ringNum = coordinates[k].length; + org.meteoinfo.geometry.shape.Polygon polygon = new org.meteoinfo.geometry.shape.Polygon(); + for (int j = 0; j < ringNum; j++) { + List points = new ArrayList<>(); + int pNum = coordinates[k][j].length; + for (int i = 0; i < pNum; i++) { + points.add(new PointZ(coordinates[k][j][i][0], coordinates[k][j][i][1], + coordinates[k][j][i][2])); + } + if (j == 0) { + polygon.setOutLine(points); + } else { + polygon.addHole(points); + } + } + polygons.add(polygon); + } + PolygonZShape polygonZShape = new PolygonZShape(); + polygonZShape.setPolygons(polygons); + + return polygonZShape; + } + } +} diff --git a/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/Geometry.java b/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/Geometry.java new file mode 100644 index 00000000..0974d4ee --- /dev/null +++ b/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/Geometry.java @@ -0,0 +1,33 @@ +package org.meteoinfo.geometry.io.geojson; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.EXISTING_PROPERTY, + property = "type" +) +@JsonSubTypes( { + @JsonSubTypes.Type(value=Point.class, name="Point" ), + @JsonSubTypes.Type(value=LineString.class, name="LineString" ), + @JsonSubTypes.Type(value=Polygon.class, name="Polygon" ), + @JsonSubTypes.Type(value=MultiPoint.class, name="MultiPoint" ), + @JsonSubTypes.Type(value=MultiLineString.class, name="MultiLineString" ), + @JsonSubTypes.Type(value=MultiPolygon.class, name="MultiPolygon" ), + @JsonSubTypes.Type(value=Feature.class, name="Feature" ), + @JsonSubTypes.Type(value=FeatureCollection.class, name="FeatureCollection" ), + @JsonSubTypes.Type(value=GeometryCollection.class, name="GeometryCollection" ) +} ) + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonPropertyOrder({"type", "coordinates", "bbox"}) +public abstract class Geometry extends GeoJSON { + @JsonCreator + public Geometry() { + super(); + } +} diff --git a/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/GeometryCollection.java b/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/GeometryCollection.java new file mode 100644 index 00000000..7d534a30 --- /dev/null +++ b/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/GeometryCollection.java @@ -0,0 +1,20 @@ +package org.meteoinfo.geometry.io.geojson; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonPropertyOrder({"type", "geometries"}) +public class GeometryCollection extends Geometry { + private final Geometry[] geometries; + + @JsonCreator + public GeometryCollection(@JsonProperty("geometries") Geometry[] geometries) { + super(); + this.geometries = geometries; + } + + public Geometry[] getGeometries() { + return geometries; + } +} diff --git a/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/LineString.java b/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/LineString.java new file mode 100644 index 00000000..55e921f7 --- /dev/null +++ b/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/LineString.java @@ -0,0 +1,27 @@ +package org.meteoinfo.geometry.io.geojson; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +@JsonInclude(Include.NON_NULL) +public class LineString extends Geometry { + private final double[][] coordinates; + private final double[] bbox; + + @JsonCreator + public LineString(@JsonProperty("coordinates") double [][] coordinates) { + super(); + this.coordinates = coordinates; + this.bbox = null; + } + + public double[][] getCoordinates() { + return coordinates; + } + + public double[] getBbox() { + return bbox; + } +} diff --git a/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/MultiLineString.java b/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/MultiLineString.java new file mode 100644 index 00000000..ce7923b9 --- /dev/null +++ b/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/MultiLineString.java @@ -0,0 +1,27 @@ +package org.meteoinfo.geometry.io.geojson; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +@JsonInclude(Include.NON_NULL) +public class MultiLineString extends Geometry { + private final double[][][] coordinates; + private final double[] bbox; + + @JsonCreator + public MultiLineString(@JsonProperty("coordinates") double [][][] coordinates) { + super(); + this.coordinates = coordinates; + this.bbox = null; + } + + public double[][][] getCoordinates() { + return coordinates; + } + + public double[] getBbox() { + return bbox; + } +} diff --git a/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/MultiPoint.java b/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/MultiPoint.java new file mode 100644 index 00000000..e3ee5925 --- /dev/null +++ b/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/MultiPoint.java @@ -0,0 +1,27 @@ +package org.meteoinfo.geometry.io.geojson; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +@JsonInclude(Include.NON_NULL) +public class MultiPoint extends Geometry { + private final double[][] coordinates; + private final double[] bbox; + + @JsonCreator + public MultiPoint(@JsonProperty("coordinates") double [][] coordinates) { + super(); + this.coordinates = coordinates; + this.bbox = null; + } + + public double[][] getCoordinates() { + return coordinates; + } + + public double[] getBbox() { + return bbox; + } +} diff --git a/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/MultiPolygon.java b/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/MultiPolygon.java new file mode 100644 index 00000000..b77ca7c1 --- /dev/null +++ b/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/MultiPolygon.java @@ -0,0 +1,27 @@ +package org.meteoinfo.geometry.io.geojson; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +@JsonInclude(Include.NON_NULL) +public class MultiPolygon extends Geometry { + private final double[][][][] coordinates; + private final double[] bbox; + + @JsonCreator + public MultiPolygon(@JsonProperty("coordinates") double [][][][] coordinates) { + super(); + this.coordinates = coordinates; + this.bbox = null; + } + + public double[][][][] getCoordinates() { + return coordinates; + } + + public double[] getBbox() { + return bbox; + } +} diff --git a/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/Point.java b/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/Point.java new file mode 100644 index 00000000..f5920be0 --- /dev/null +++ b/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/Point.java @@ -0,0 +1,27 @@ +package org.meteoinfo.geometry.io.geojson; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +@JsonInclude(Include.NON_NULL) +public class Point extends Geometry { + private final double[] coordinates; + private final double[] bbox; + + @JsonCreator + public Point(@JsonProperty("coordinates") double [] coordinates) { + super(); + this.coordinates = coordinates; + this.bbox = null; + } + + public double[] getCoordinates() { + return coordinates; + } + + public double[] getBbox() { + return bbox; + } +} diff --git a/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/Polygon.java b/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/Polygon.java new file mode 100644 index 00000000..3fcc0720 --- /dev/null +++ b/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/Polygon.java @@ -0,0 +1,27 @@ +package org.meteoinfo.geometry.io.geojson; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +@JsonInclude(Include.NON_NULL) +public class Polygon extends Geometry { + private final double[][][] coordinates; + private final double[] bbox; + + @JsonCreator + public Polygon(@JsonProperty("coordinates") double [][][] coordinates) { + super(); + this.coordinates = coordinates; + this.bbox = null; + } + + public double[][][] getCoordinates() { + return coordinates; + } + + public double[] getBbox() { + return bbox; + } +} diff --git a/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/shape/PointZ.java b/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/shape/PointZ.java index b216660f..202455c2 100644 --- a/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/shape/PointZ.java +++ b/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/shape/PointZ.java @@ -94,6 +94,7 @@ public class PointZ extends PointD implements Cloneable{ * To double array * @return Double array */ + @Override public double[] toArray() { return new double[]{X, Y, Z}; } @@ -102,6 +103,7 @@ public class PointZ extends PointD implements Cloneable{ * To float array * @return Float array */ + @Override public float[] toFloatArray() { return new float[]{(float) X, (float) Y, (float) Z}; } diff --git a/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/shape/PointZShape.java b/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/shape/PointZShape.java index 194bf9aa..5ed75b19 100644 --- a/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/shape/PointZShape.java +++ b/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/shape/PointZShape.java @@ -38,6 +38,15 @@ public class PointZShape extends PointShape { this.point = new PointZ(); this.updateExtent((PointZ)this.point); } + + /** + * Constructor + * @param pointZ The PointZ object + */ + public PointZShape(PointZ pointZ) { + this.point = pointZ; + this.updateExtent(pointZ); + } /** * Constructor diff --git a/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/shape/Polygon.java b/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/shape/Polygon.java index 1050cb8c..47e9f38d 100644 --- a/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/shape/Polygon.java +++ b/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/shape/Polygon.java @@ -77,19 +77,6 @@ public class Polygon { return this._holeLines; } - /** - * Get hole lines - * - * @return hole lines - */ - public List> getHoleLines_bak() { - List> hlines = new ArrayList<>(); - for (List hline : _holeLines){ - hlines.add((List)hline); - } - return hlines; - } - /** * Get a hole line * @param idx Index diff --git a/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/shape/PolygonShape.java b/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/shape/PolygonShape.java index b4b2d019..0ae8d660 100644 --- a/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/shape/PolygonShape.java +++ b/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/shape/PolygonShape.java @@ -150,6 +150,14 @@ public class PolygonShape extends Shape implements Cloneable { return ShapeTypes.POLYGON; } + /** + * Get is multi polygon or not + * @return Multi polygon or not + */ + public boolean isMultiPolygon() { + return this._numParts > 1; + } + /** * To geometry method * @@ -279,6 +287,15 @@ public class PolygonShape extends Shape implements Cloneable { return _polygons; } + /** + * Get a polygon + * @param index The polygon index + * @return The polygon + */ + public Polygon getPolygon(int index) { + return _polygons.get(index); + } + /** * Set polygons * diff --git a/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/shape/PolylineShape.java b/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/shape/PolylineShape.java index 9204e95c..72c835b5 100644 --- a/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/shape/PolylineShape.java +++ b/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/shape/PolylineShape.java @@ -58,6 +58,14 @@ public class PolylineShape extends Shape implements Cloneable { _polylines = new ArrayList<>(); } + /** + * Constructor + * @param points Point list + */ + public PolylineShape(List points) { + this.setPoints(points); + } + /** * Constructor * @@ -109,6 +117,14 @@ public class PolylineShape extends Shape implements Cloneable { return ShapeTypes.POLYLINE; } + /** + * Get is multi line or not + * @return Multi line or not + */ + public boolean isMultiLine() { + return this._numParts > 1; + } + /** * To geometry method * diff --git a/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/shape/PolylineZShape.java b/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/shape/PolylineZShape.java index 559a6560..56c48929 100644 --- a/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/shape/PolylineZShape.java +++ b/meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/shape/PolylineZShape.java @@ -41,6 +41,14 @@ public class PolylineZShape extends PolylineShape { public PolylineZShape() { super(); } + + /** + * Constructor + * @param points Points + */ + public PolylineZShape(List points) { + this.setPoints(points); + } /** * Constructor diff --git a/meteoinfo-lab/milconfig.xml b/meteoinfo-lab/milconfig.xml index e8cf14c4..ab03809d 100644 --- a/meteoinfo-lab/milconfig.xml +++ b/meteoinfo-lab/milconfig.xml @@ -1,32 +1,32 @@ - - - - - - - - - - - - + - + + + + + + + + + + + + - - - + + + - - - + + + @@ -34,5 +34,5 @@
- + diff --git a/meteoinfo-lab/pylib/mipylib/geolib/milayer$py.class b/meteoinfo-lab/pylib/mipylib/geolib/milayer$py.class index 6b5d57ab..720e113d 100644 Binary files a/meteoinfo-lab/pylib/mipylib/geolib/milayer$py.class and b/meteoinfo-lab/pylib/mipylib/geolib/milayer$py.class differ diff --git a/meteoinfo-lab/pylib/mipylib/geolib/milayer.py b/meteoinfo-lab/pylib/mipylib/geolib/milayer.py index 5a782ecd..25459971 100644 --- a/meteoinfo-lab/pylib/mipylib/geolib/milayer.py +++ b/meteoinfo-lab/pylib/mipylib/geolib/milayer.py @@ -17,6 +17,7 @@ from org.meteoinfo.projection import ProjectionUtil, KnownCoordinateSystems from org.meteoinfo.geometry.shape import PolygonShape, ShapeTypes from org.meteoinfo.geo.analysis import GeometryUtil from org.meteoinfo.geo.util import GeoProjectionUtil +from org.meteoinfo.geo.io import GeoJSONReader, GeoJSONWriter class MILayer(object): @@ -277,6 +278,14 @@ class MILayer(object): for shape, field in zip(shapes, fields): self._layer.editAddShape(shape, field) + def del_shape(self, shape): + """ + Delete a shape. + + :param shape: (*Shape or int*) The shape or shape index to be deleted. + """ + self._layer.editRemoveShape(shape) + def copy(self): """ Copy the layer. @@ -547,7 +556,7 @@ class MILayer(object): else: self._layer.saveFile(fn, encoding) - def savekml(self, fn): + def save_kml(self, fn): """ Save layer as KML file. @@ -555,7 +564,7 @@ class MILayer(object): """ self._layer.saveAsKMLFile(fn) - def savebil(self, fn, proj=None): + def save_bil(self, fn, proj=None): """ Save layer as bil file. @@ -567,6 +576,27 @@ class MILayer(object): else: self._layer.saveFile(fn, proj) + def save_geojson(self, fn): + """ + Save layer as GeoJSON file. + + :param fn: (*str*) GeoJSON file name. + """ + features = GeoJSONWriter.write(self._layer) + with open(fn, 'w') as f: + f.write("{\n") + f.write(""""type": "FeatureCollection",\n""") + f.write(""""features": [\n""") + for i in range(features.getNumFeatures()): + feature = features.getFeature(i) + f.write(feature.toString()) + if i < features.getNumFeatures() - 1: + f.write(",\n") + else: + f.write("\n") + f.write("]\n") + f.write("}") + class MIXYListData(): def __init__(self, data=None): diff --git a/pom.xml b/pom.xml index f06df31d..ca78d2a9 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ UTF-8 1.8 - 3.7.8 + 3.7.9 8 8 8