Introduce GIS utilities.
authorMathieu Baudier <mbaudier@argeo.org>
Mon, 9 May 2022 08:50:15 +0000 (10:50 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Mon, 9 May 2022 08:50:15 +0000 (10:50 +0200)
org.argeo.app.core/OSGI-INF/geoToolsTest.xml [new file with mode: 0644]
org.argeo.app.core/bnd.bnd
org.argeo.app.core/src/org/argeo/app/geo/GeoToolsTest.java [new file with mode: 0644]
org.argeo.app.core/src/org/argeo/app/geo/GeoUtils.java [new file with mode: 0644]
org.argeo.app.core/src/org/argeo/app/geo/GpxUtils.java [new file with mode: 0644]
org.argeo.app.theme.default/icons/types/svg/inbox.svg [new file with mode: 0644]

diff --git a/org.argeo.app.core/OSGI-INF/geoToolsTest.xml b/org.argeo.app.core/OSGI-INF/geoToolsTest.xml
new file mode 100644 (file)
index 0000000..68a53ab
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="GeoTools Test">
+   <implementation class="org.djapps.on.apaf.GeoToolsTest"/>
+</scr:component>
index 8604ec964a88770bb2c93e014a80c3490b91a6af..9d8228bec121fb4c629a6beef6490639c6b4e4ed 100644 (file)
@@ -6,6 +6,7 @@ OSGI-INF/maintenanceService.xml,\
 OSGI-INF/dbk4Converter.xml,\
 
 Import-Package:\
+tech.units.indriya.unit,\
 org.osgi.service.useradmin,\
 javax.jcr.nodetype,\
 javax.jcr.security,\
diff --git a/org.argeo.app.core/src/org/argeo/app/geo/GeoToolsTest.java b/org.argeo.app.core/src/org/argeo/app/geo/GeoToolsTest.java
new file mode 100644 (file)
index 0000000..a771196
--- /dev/null
@@ -0,0 +1,221 @@
+package org.argeo.app.geo;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.InputStreamReader;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.geotools.data.DataUtilities;
+import org.geotools.data.DefaultTransaction;
+import org.geotools.data.Transaction;
+import org.geotools.data.collection.ListFeatureCollection;
+import org.geotools.data.shapefile.ShapefileDataStore;
+import org.geotools.data.shapefile.ShapefileDataStoreFactory;
+import org.geotools.data.simple.SimpleFeatureCollection;
+import org.geotools.data.simple.SimpleFeatureSource;
+import org.geotools.data.simple.SimpleFeatureStore;
+import org.geotools.feature.simple.SimpleFeatureBuilder;
+import org.geotools.geometry.jts.JTSFactoryFinder;
+import org.geotools.swing.data.JFileDataStoreChooser;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.geom.Point;
+import org.opengis.feature.simple.SimpleFeature;
+import org.opengis.feature.simple.SimpleFeatureType;
+
+public class GeoToolsTest {
+       public GeoToolsTest() {
+
+       }
+
+       public void init() {
+               try {
+                       main(null);
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
+       }
+
+       public void destroy() {
+
+       }
+
+       public static void main(String[] args) throws Exception {
+               final SimpleFeatureType TYPE = DataUtilities.createType("Location", "the_geom:Point:srid=4326," + // <- the
+               // geometry
+               // attribute:
+               // Point
+               // type
+                               "name:String," + // <- a String attribute
+                               "number:Integer" // a number attribute
+               );
+               final SimpleFeatureType TYPE_HULL = DataUtilities.createType("Hull", "the_geom:MultiPolygon:srid=4326");
+               System.out.println("TYPE:" + TYPE);
+
+               /*
+                * A list to collect features as we create them.
+                */
+               List<SimpleFeature> features = new ArrayList<>();
+               List<Coordinate> coordinates = new ArrayList<>();
+
+               /*
+                * GeometryFactory will be used to create the geometry attribute of each
+                * feature, using a Point object for the location.
+                */
+               GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory();
+
+               SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(TYPE);
+
+               try (BufferedReader reader = new BufferedReader(
+                               new InputStreamReader(GeoToolsTest.class.getResourceAsStream("/org/djapps/on/apaf/locations.csv")))) {
+                       /* First line of the data file is the header */
+                       String line = reader.readLine();
+                       System.out.println("Header: " + line);
+
+                       for (line = reader.readLine(); line != null; line = reader.readLine()) {
+                               if (line.trim().length() > 0) { // skip blank lines
+                                       String[] tokens = line.split("\\,");
+
+                                       double latitude = Double.parseDouble(tokens[0]);
+                                       double longitude = Double.parseDouble(tokens[1]);
+                                       String name = tokens[2].trim();
+                                       int number = Integer.parseInt(tokens[3].trim());
+
+                                       /* Longitude (= x coord) first ! */
+                                       Coordinate coordinate = new Coordinate(longitude, latitude);
+                                       coordinates.add(coordinate);
+                                       Point point = geometryFactory.createPoint(coordinate);
+
+                                       featureBuilder.add(point);
+                                       featureBuilder.add(name);
+                                       featureBuilder.add(number);
+                                       SimpleFeature feature = featureBuilder.buildFeature(null);
+                                       features.add(feature);
+                               }
+                       }
+               }
+
+               LineString lineString = geometryFactory
+                               .createLineString(coordinates.toArray(new Coordinate[coordinates.size()]));
+               Geometry convexHull = lineString.convexHull();
+               System.out.println(convexHull.toText());
+               SimpleFeatureBuilder hullFeatureBuilder = new SimpleFeatureBuilder(TYPE_HULL);
+               hullFeatureBuilder.add(convexHull);
+               SimpleFeature hull = hullFeatureBuilder.buildFeature(null);
+
+               /*
+                * Get an output file name and create the new shapefile
+                */
+               File newFile = getNewShapeFile();
+
+               ShapefileDataStoreFactory dataStoreFactory = new ShapefileDataStoreFactory();
+
+               Map<String, Serializable> params = new HashMap<>();
+               params.put("url", newFile.toURI().toURL());
+               params.put("create spatial index", Boolean.TRUE);
+
+               ShapefileDataStore newDataStore = (ShapefileDataStore) dataStoreFactory.createNewDataStore(params);
+
+               /*
+                * TYPE is used as a template to describe the file contents
+                */
+               newDataStore.createSchema(TYPE_HULL);
+
+               /*
+                * Write the features to the shapefile
+                */
+               Transaction transaction = new DefaultTransaction("create");
+
+               String typeName = newDataStore.getTypeNames()[0];
+               SimpleFeatureSource featureSource = newDataStore.getFeatureSource(typeName);
+               SimpleFeatureType SHAPE_TYPE = featureSource.getSchema();
+               /*
+                * The Shapefile format has a couple limitations: - "the_geom" is always first,
+                * and used for the geometry attribute name - "the_geom" must be of type Point,
+                * MultiPoint, MuiltiLineString, MultiPolygon - Attribute names are limited in
+                * length - Not all data types are supported (example Timestamp represented as
+                * Date)
+                *
+                * Each data store has different limitations so check the resulting
+                * SimpleFeatureType.
+                */
+               System.out.println("SHAPE:" + SHAPE_TYPE);
+
+               if (featureSource instanceof SimpleFeatureStore) {
+                       SimpleFeatureStore featureStore = (SimpleFeatureStore) featureSource;
+                       /*
+                        * SimpleFeatureStore has a method to add features from a
+                        * SimpleFeatureCollection object, so we use the ListFeatureCollection class to
+                        * wrap our list of features.
+                        */
+                       SimpleFeatureCollection collection = new ListFeatureCollection(TYPE, Collections.singletonList(hull));
+                       featureStore.setTransaction(transaction);
+                       try {
+                               featureStore.addFeatures(collection);
+                               transaction.commit();
+                       } catch (Exception problem) {
+                               problem.printStackTrace();
+                               transaction.rollback();
+                       } finally {
+                               transaction.close();
+                       }
+               } else {
+                       System.out.println(typeName + " does not support read/write access");
+               }
+//             if (featureSource instanceof SimpleFeatureStore) {
+//                     SimpleFeatureStore featureStore = (SimpleFeatureStore) featureSource;
+//                     /*
+//                      * SimpleFeatureStore has a method to add features from a
+//                      * SimpleFeatureCollection object, so we use the ListFeatureCollection class to
+//                      * wrap our list of features.
+//                      */
+//                     SimpleFeatureCollection collection = new ListFeatureCollection(TYPE, features);
+//                     featureStore.setTransaction(transaction);
+//                     try {
+//                             featureStore.addFeatures(collection);
+//                             transaction.commit();
+//                     } catch (Exception problem) {
+//                             problem.printStackTrace();
+//                             transaction.rollback();
+//                     } finally {
+//                             transaction.close();
+//                     }
+//             } else {
+//                     System.out.println(typeName + " does not support read/write access");
+//             }
+       }
+
+       /**
+        * Prompt the user for the name and path to use for the output shapefile
+        *
+        * @param csvFile the input csv file used to create a default shapefile name
+        * @return name and path for the shapefile as a new File object
+        */
+       private static File getNewShapeFile() {
+//        String path = csvFile.getAbsolutePath();
+//        String newPath = path.substring(0, path.length() - 4) + ".shp";
+
+               JFileDataStoreChooser chooser = new JFileDataStoreChooser("shp");
+               chooser.setDialogTitle("Save shapefile");
+//        chooser.setSelectedFile(new File(newPath));
+
+               int returnVal = chooser.showSaveDialog(null);
+
+               if (returnVal != JFileDataStoreChooser.APPROVE_OPTION) {
+                       // the user cancelled the dialog
+                       System.exit(0);
+               }
+
+               File newFile = chooser.getSelectedFile();
+
+               return newFile;
+       }
+
+}
diff --git a/org.argeo.app.core/src/org/argeo/app/geo/GeoUtils.java b/org.argeo.app.core/src/org/argeo/app/geo/GeoUtils.java
new file mode 100644 (file)
index 0000000..8da7c4e
--- /dev/null
@@ -0,0 +1,44 @@
+package org.argeo.app.geo;
+
+import javax.measure.Quantity;
+import javax.measure.quantity.Area;
+
+import org.geotools.geometry.jts.JTS;
+import org.geotools.referencing.CRS;
+import org.geotools.referencing.crs.DefaultGeographicCRS;
+import org.locationtech.jts.geom.Point;
+import org.locationtech.jts.geom.Polygon;
+import org.opengis.feature.simple.SimpleFeature;
+import org.opengis.geometry.MismatchedDimensionException;
+import org.opengis.referencing.FactoryException;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.operation.MathTransform;
+import org.opengis.referencing.operation.TransformException;
+
+import si.uom.SI;
+import tech.units.indriya.quantity.Quantities;
+
+/** Utilities around geographical format, mostly wrapping GeoTools patterns. */
+public class GeoUtils {
+
+       /** In square meters. */
+       public static Quantity<Area> calcArea(SimpleFeature feature) {
+               try {
+                       Polygon p = (Polygon) feature.getDefaultGeometry();
+                       Point centroid = p.getCentroid();
+                       String code = "AUTO:42001," + centroid.getX() + "," + centroid.getY();
+                       CoordinateReferenceSystem auto = CRS.decode(code);
+
+                       MathTransform transform = CRS.findMathTransform(DefaultGeographicCRS.WGS84, auto);
+
+                       Polygon projed = (Polygon) JTS.transform(p, transform);
+                       return Quantities.getQuantity(projed.getArea(), SI.SQUARE_METRE);
+               } catch (MismatchedDimensionException | FactoryException | TransformException e) {
+                       throw new IllegalStateException("Cannot claculate area of feature");
+               }
+       }
+
+       /** Singleton. */
+       private GeoUtils() {
+       }
+}
diff --git a/org.argeo.app.core/src/org/argeo/app/geo/GpxUtils.java b/org.argeo.app.core/src/org/argeo/app/geo/GpxUtils.java
new file mode 100644 (file)
index 0000000..be028d3
--- /dev/null
@@ -0,0 +1,68 @@
+package org.argeo.app.geo;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.geotools.data.DataUtilities;
+import org.geotools.feature.SchemaException;
+import org.geotools.feature.simple.SimpleFeatureBuilder;
+import org.geotools.geometry.jts.JTSFactoryFinder;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.Polygon;
+import org.opengis.feature.simple.SimpleFeature;
+import org.opengis.feature.simple.SimpleFeatureType;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/** Utilities around the GPX format. */
+public class GpxUtils {
+
+       public static SimpleFeature parseGpxToPolygon(InputStream in) {
+               try {
+                       final SimpleFeatureType TYPE = DataUtilities.createType("Area", "the_geom:Polygon:srid=4326");
+                       SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(TYPE);
+
+                       GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory();
+                       List<Coordinate> coordinates = new ArrayList<>();
+                       SAXParserFactory factory = SAXParserFactory.newInstance();
+                       Double[] startCoord = new Double[2];
+                       SAXParser saxParser = factory.newSAXParser();
+
+                       saxParser.parse(in, new DefaultHandler() {
+
+                               @Override
+                               public void startElement(String uri, String localName, String qName, Attributes attributes)
+                                               throws SAXException {
+                                       if ("trkpt".equals(qName)) {
+                                               Double latitude = Double.parseDouble(attributes.getValue("lat"));
+                                               Double longitude = Double.parseDouble(attributes.getValue("lon"));
+                                               Coordinate coordinate = new Coordinate(longitude, latitude);
+                                               coordinates.add(coordinate);
+                                       }
+                               }
+
+                       });
+                       // close the line string
+                       coordinates.add(coordinates.get(0));
+
+                       Polygon polygon = geometryFactory.createPolygon(coordinates.toArray(new Coordinate[coordinates.size()]));
+                       featureBuilder.add(polygon);
+                       SimpleFeature area = featureBuilder.buildFeature(null);
+                       return area;
+               } catch (ParserConfigurationException | SAXException | IOException | SchemaException e) {
+                       throw new RuntimeException("Cannot convert GPX", e);
+               }
+       }
+
+       /** Singleton. */
+       private GpxUtils() {
+       }
+}
diff --git a/org.argeo.app.theme.default/icons/types/svg/inbox.svg b/org.argeo.app.theme.default/icons/types/svg/inbox.svg
new file mode 100644 (file)
index 0000000..d1a171c
--- /dev/null
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">
+  <defs>
+    <style>.cls-1{fill:none;stroke:#f3964d;stroke-linecap:round;stroke-linejoin:round;stroke-width:20px;}.cls-2{fill:#f3964d;}</style>
+  </defs>
+  <title>ascending</title>
+  <g id="Plus">
+    <line class="cls-1" x1="119" x2="119" y1="128.38" y2="384.84"/>
+    <polygon class="cls-2" points="81.1 139.47 119 73.84 156.9 139.47 81.1 139.47"/>
+    <line class="cls-1" x1="188" x2="406" y1="376.84" y2="376.84"/>
+    <line class="cls-1" x1="188" x2="370" y1="316.84" y2="316.84"/>
+    <line class="cls-1" x1="188" x2="318" y1="250.84" y2="250.84"/>
+    <line class="cls-1" x1="188" x2="270" y1="184.84" y2="184.84"/>
+  </g>
+</svg>