Skip to content

Commit

Permalink
Add mapper for geoJsonFormat.
Browse files Browse the repository at this point in the history
  • Loading branch information
Andy2003 committed May 29, 2024
1 parent 93adcb7 commit ff6a85c
Show file tree
Hide file tree
Showing 5 changed files with 308 additions and 89 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j Spatial.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package org.neo4j.gis.spatial.functions;

import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.io.ParseException;
import org.locationtech.jts.io.WKTReader;
import org.neo4j.gis.spatial.utilities.GeoJsonUtils;
import org.neo4j.procedure.Description;
import org.neo4j.procedure.Name;
import org.neo4j.procedure.UserFunction;

public class SpatialFunctions {

@UserFunction("spatial.convert.wktToGeoJson")
@Description("Converts a WKT to GeoJson structure")
public Object wktToGeoJson(@Name("wkt") String wkt) throws ParseException {
if (wkt == null) {
return null;
}
WKTReader wktReader = new WKTReader();
Geometry geometry = wktReader.read(wkt);
return GeoJsonUtils.toGeoJsonStructure(geometry);
}
}
82 changes: 82 additions & 0 deletions src/main/java/org/neo4j/gis/spatial/utilities/GeoJsonUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j Spatial.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package org.neo4j.gis.spatial.utilities;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryCollection;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.Polygon;


public class GeoJsonUtils {

private GeoJsonUtils() {
}

public static Map<String, Object> toGeoJsonStructure(Geometry geometry) {
return Map.of(
"type", geometry.getGeometryType(),
"coordinates", getCoordinates(geometry)
);
}

private static List<?> getCoordinates(Geometry geometry) {
if (geometry instanceof Point point) {
return getPoint(point.getCoordinate());
}
if (geometry instanceof LineString lineString) {
return Arrays.stream(lineString.getCoordinates()).map(GeoJsonUtils::getPoint).toList();
}
if (geometry instanceof Polygon polygon) {
return Stream.concat(
Stream.of(polygon.getExteriorRing()),
IntStream.range(0, polygon.getNumInteriorRing())
.mapToObj(polygon::getInteriorRingN)
)
.map(GeoJsonUtils::getCoordinates)
.toList();
}
if (geometry instanceof GeometryCollection geometryCollection) {
return IntStream.range(0, geometryCollection.getNumGeometries())
.mapToObj(geometryCollection::getGeometryN)
.map(GeoJsonUtils::getCoordinates)
.toList();
}
throw new IllegalArgumentException("Unsupported geometry type: " + geometry.getGeometryType());
}

private static List<Object> getPoint(Coordinate coordinate) {
if (Double.isNaN(coordinate.getZ())) {
return List.of(coordinate.getX(), coordinate.getY());
}
return List.of(
coordinate.getX(),
coordinate.getY(),
coordinate.getZ());
}
}
120 changes: 120 additions & 0 deletions src/test/java/org/neo4j/gis/spatial/AbstractApiTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j Spatial.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package org.neo4j.gis.spatial;

import static org.neo4j.configuration.GraphDatabaseSettings.DEFAULT_DATABASE_NAME;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.neo4j.configuration.GraphDatabaseSettings;
import org.neo4j.dbms.api.DatabaseManagementService;
import org.neo4j.exceptions.KernelException;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.ResourceIterator;
import org.neo4j.graphdb.Transaction;
import org.neo4j.internal.helpers.collection.Iterators;
import org.neo4j.io.fs.FileUtils;
import org.neo4j.kernel.api.procedure.GlobalProcedures;
import org.neo4j.kernel.internal.GraphDatabaseAPI;
import org.neo4j.test.TestDatabaseManagementServiceBuilder;

public abstract class AbstractApiTest {

private DatabaseManagementService databases;
protected GraphDatabaseService db;

@BeforeEach
public void setUp() throws KernelException, IOException {
Path dbRoot = new File("target/procedures").toPath();
FileUtils.deleteDirectory(dbRoot);
databases = new TestDatabaseManagementServiceBuilder(dbRoot)
.setConfig(GraphDatabaseSettings.procedure_unrestricted, List.of("spatial.*"))
.impermanent()
.build();
db = databases.database(DEFAULT_DATABASE_NAME);
registerApiProceduresAndFunctions();
}

protected abstract void registerApiProceduresAndFunctions() throws KernelException;

protected void registerProceduresAndFunctions(Class<?> api) throws KernelException {
GlobalProcedures procedures = ((GraphDatabaseAPI) db).getDependencyResolver()
.resolveDependency(GlobalProcedures.class);
procedures.registerProcedure(api);
procedures.registerFunction(api);
}

@AfterEach
public void tearDown() {
databases.shutdown();
}

protected long execute(String statement) {
return execute(statement, null);
}

protected long execute(String statement, Map<String, Object> params) {
try (Transaction tx = db.beginTx()) {
if (params == null) {
params = Collections.emptyMap();
}
long count = Iterators.count(tx.execute(statement, params));
tx.commit();
return count;
}
}

protected void executeWrite(String call) {
try (Transaction tx = db.beginTx()) {
tx.execute(call).accept(v -> true);
tx.commit();
}
}

protected Node createNode(String call, String column) {
return (Node) executeObject(call, null, column);
}

protected Object executeObject(String call, String column) {
return executeObject(call, null, column);
}

protected Object executeObject(String call, Map<String, Object> params, String column) {
Object obj;
try (Transaction tx = db.beginTx()) {
if (params == null) {
params = Collections.emptyMap();
}
ResourceIterator<Object> values = tx.execute(call, params).columnAs(column);
obj = values.next();
values.close();
tx.commit();
}
return obj;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j Spatial.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package org.neo4j.gis.spatial.functions;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;

import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.neo4j.exceptions.KernelException;
import org.neo4j.gis.spatial.AbstractApiTest;

public class SpatialFunctionsTest extends AbstractApiTest {

@Override
protected void registerApiProceduresAndFunctions() throws KernelException {
registerProceduresAndFunctions(SpatialFunctions.class);
}

@Test
public void wktToGeoJson() {
String wkt = "MULTIPOLYGON(((15.3 60.2, 15.3 60.4, 15.7 60.4, 15.7 60.2, 15.3 60.2)))";
Object json = executeObject("return spatial.convert.wktToGeoJson($wkt) as json", Map.of("wkt", wkt), "json");
assertThat(json, equalTo(Map.of(
"type", "MultiPolygon",
"coordinates", List.of( // MultiPolygon
List.of( // Polygon
List.of( // LineString
List.of(15.3, 60.2),
List.of(15.3, 60.4),
List.of(15.7, 60.4),
List.of(15.7, 60.2),
List.of(15.3, 60.2)
)
)
)
)));
}
}
Loading

0 comments on commit ff6a85c

Please sign in to comment.