From b02bda02f5a01e2f8b197f2fa1a76f8a5caf8a79 Mon Sep 17 00:00:00 2001 From: wyq Date: Wed, 17 Jan 2024 10:06:32 +0800 Subject: [PATCH] support output GeoJSON file from Layer --- .../java/org/meteoinfo/common/PointD.java | 19 +- .../meteoinfo/common/colors/ColorUtil.java | 9 + .../org/meteoinfo/common/util/GlobalUtil.java | 2 +- .../org/meteoinfo/geo/io/GeoJSONReader.java | 77 ++++ .../org/meteoinfo/geo/io/GeoJSONWriter.java | 63 ++++ .../org/meteoinfo/geo/layer/VectorLayer.java | 11 + meteoinfo-geometry/pom.xml | 10 + .../geometry/io/geojson/Feature.java | 46 +++ .../io/geojson/FeatureCollection.java | 41 +++ .../geometry/io/geojson/GeoJSON.java | 58 +++ .../geometry/io/geojson/GeoJSONFactory.java | 65 ++++ .../geometry/io/geojson/GeoJSONUtil.java | 341 ++++++++++++++++++ .../geometry/io/geojson/Geometry.java | 33 ++ .../io/geojson/GeometryCollection.java | 20 + .../geometry/io/geojson/LineString.java | 27 ++ .../geometry/io/geojson/MultiLineString.java | 27 ++ .../geometry/io/geojson/MultiPoint.java | 27 ++ .../geometry/io/geojson/MultiPolygon.java | 27 ++ .../meteoinfo/geometry/io/geojson/Point.java | 27 ++ .../geometry/io/geojson/Polygon.java | 27 ++ .../org/meteoinfo/geometry/shape/PointZ.java | 2 + .../meteoinfo/geometry/shape/PointZShape.java | 9 + .../org/meteoinfo/geometry/shape/Polygon.java | 13 - .../geometry/shape/PolygonShape.java | 17 + .../geometry/shape/PolylineShape.java | 16 + .../geometry/shape/PolylineZShape.java | 8 + meteoinfo-lab/milconfig.xml | 40 +- .../pylib/mipylib/geolib/milayer$py.class | Bin 37163 -> 39266 bytes meteoinfo-lab/pylib/mipylib/geolib/milayer.py | 34 +- pom.xml | 2 +- 30 files changed, 1060 insertions(+), 38 deletions(-) create mode 100644 meteoinfo-geo/src/main/java/org/meteoinfo/geo/io/GeoJSONReader.java create mode 100644 meteoinfo-geo/src/main/java/org/meteoinfo/geo/io/GeoJSONWriter.java create mode 100644 meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/Feature.java create mode 100644 meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/FeatureCollection.java create mode 100644 meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/GeoJSON.java create mode 100644 meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/GeoJSONFactory.java create mode 100644 meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/GeoJSONUtil.java create mode 100644 meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/Geometry.java create mode 100644 meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/GeometryCollection.java create mode 100644 meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/LineString.java create mode 100644 meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/MultiLineString.java create mode 100644 meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/MultiPoint.java create mode 100644 meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/MultiPolygon.java create mode 100644 meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/Point.java create mode 100644 meteoinfo-geometry/src/main/java/org/meteoinfo/geometry/io/geojson/Polygon.java 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 6b5d57ab683c6f4f7421feb6266b98ba58f75279..720e113d4b3091c27a8df1d92a622f4f0b6a7d67 100644 GIT binary patch literal 39266 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 Ok@rNl(Mx~iqW=R(D%&;y literal 37163 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 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