From cf019befd58f851865fe28ba3860ff6d28628c51 Mon Sep 17 00:00:00 2001 From: Claus Nagel Date: Sat, 2 Dec 2023 13:04:02 +0100 Subject: [PATCH] reworked geometry properties --- .../database/geometry/GeometryBuilder.java | 175 +++++++++++------- .../citydb/database/geometry/Properties.java | 2 +- .../database/geometry/PropertiesBuilder.java | 67 +++---- .../geometryProperties.schema.json | 13 +- 4 files changed, 152 insertions(+), 105 deletions(-) diff --git a/citydb-database/src/main/java/org/citydb/database/geometry/GeometryBuilder.java b/citydb-database/src/main/java/org/citydb/database/geometry/GeometryBuilder.java index 21e5b34a..64abb7bf 100644 --- a/citydb-database/src/main/java/org/citydb/database/geometry/GeometryBuilder.java +++ b/citydb-database/src/main/java/org/citydb/database/geometry/GeometryBuilder.java @@ -32,43 +32,64 @@ public class GeometryBuilder { public Geometry buildGeometry(Geometry geometry, JSONObject properties) throws GeometryException { if (geometry != null && properties != null) { - Map hierarchy = buildHierarchy(properties); - if (!hierarchy.isEmpty()) { - if (hierarchy.get(0).containsKey(Properties.JSON_KEY_PARENT)) { - throw new GeometryException("The geometry hierarchy lacks a root item."); - } - - Map> parents = new HashMap<>(hierarchy.size()); + JSONArray children = properties.getJSONArray(Properties.JSON_KEY_CHILDREN); + if (children != null && !children.isEmpty()) { List> primitives = getPrimitives(geometry); - try { - for (Map.Entry entry : hierarchy.entrySet()) { - Geometry item = buildGeometry(entry.getValue(), parents, primitives); - parents.put(entry.getKey(), item); - } - } catch (Exception e) { - throw new GeometryException("Failed to rebuild geometry hierarchy.", e); - } + geometry = buildGeometry(properties, null, primitives); + + Map> geometries = new HashMap<>(children.size() + 1); + geometries.put(-1, geometry); - if (!parents.isEmpty()) { - geometry = parents.get(0); + for (int i = 0; i < children.size(); i++) { + buildGeometry(i, children, geometries, primitives); + } + } else { + GeometryType type = getType(properties); + if (geometry.getGeometryType() != type) { + geometry = castGeometry(geometry, type); } } - processMetadata(geometry, properties); + geometry.setObjectId(properties.getString(Properties.JSON_KEY_OBJECT_ID)); + if (properties.getBooleanValue(Properties.JSON_KEY_IS_2D, false)) { + geometry.force2D(); + } } return geometry; } - private Geometry buildGeometry(JSONObject item, Map> parents, List> primitives) throws GeometryException { - GeometryType type = GeometryType.fromDatabaseValue(item.getIntValue(Properties.JSON_KEY_TYPE, -1)); - if (type == null) { - throw new GeometryException("Missing geometry type property."); + private Geometry buildGeometry(int childIndex, JSONArray children, Map> geometries, List> primitives) throws GeometryException { + Geometry geometry = geometries.get(childIndex); + if (geometry == null) { + JSONObject child = children.getJSONObject(childIndex); + if (child == null) { + throw new GeometryException("Invalid JSON object in children array at index " + childIndex + "."); + } + + Integer parentIndex = child.getInteger(Properties.JSON_KEY_PARENT); + if (parentIndex == null) { + parentIndex = -1; + } else if (parentIndex < 0 || parentIndex >= children.size()) { + throw new GeometryException("Parent index out of bounds."); + } + + Geometry parent = geometries.get(parentIndex); + if (parent == null) { + parent = buildGeometry(parentIndex, children, geometries, primitives); + } + + geometry = buildGeometry(child, parent, primitives) + .setObjectId(child.getString(Properties.JSON_KEY_OBJECT_ID)); + geometries.put(childIndex, geometry); } - Geometry parent = getParent(item, parents); + return geometry; + } + + private Geometry buildGeometry(JSONObject item, Geometry parent, List> primitives) throws GeometryException { Geometry geometry = null; - switch (type) { + switch (getType(item)) { case POINT: geometry = getPrimitive(item, primitives, Point.class); if (parent instanceof MultiPoint) { @@ -120,39 +141,43 @@ private Geometry buildGeometry(JSONObject item, Map> par } if (geometry != null) { - return geometry.setObjectId(item.getString(Properties.JSON_KEY_OBJECT_ID)); + return geometry; } else { - throw new GeometryException("Failed to parse geometry hierarchy item."); + throw new GeometryException("Failed to parse geometry object."); } } - private > T getPrimitive(JSONObject item, List> primitives, Class type) throws GeometryException { - Integer index = item.getInteger(Properties.JSON_KEY_GEOMETRY_INDEX); - if (index == null) { - throw new GeometryException("Missing geometry index."); - } else if (index < 0 || index >= primitives.size()) { - throw new GeometryException("Geometry index out of bounds."); - } - - Geometry primitive = primitives.get(index); - if (type.isInstance(primitive)) { - return type.cast(primitive); - } else { - throw new GeometryException("Invalid primitive type " + primitive.getGeometryType() + "."); - } - } - - private Geometry getParent(JSONObject item, Map> parents) throws GeometryException { - Integer index = item.getInteger(Properties.JSON_KEY_PARENT); - if (index != null) { - Geometry parent = parents.get(index); - if (parent == null) { - throw new GeometryException("Parent index out of bounds."); + private Geometry castGeometry(Geometry geometry, GeometryType type) throws GeometryException { + try { + switch (type) { + case POINT: + return getPrimitives(geometry, Point.class).get(0); + case MULTI_POINT: + return MultiPoint.of(getPrimitives(geometry, Point.class)); + case LINE_STRING: + return getPrimitives(geometry, LineString.class).get(0); + case MULTI_LINE_STRING: + return MultiLineString.of(getPrimitives(geometry, LineString.class)); + case POLYGON: + return getPrimitives(geometry, Polygon.class).get(0); + case MULTI_SURFACE: + return MultiSurface.of(getPrimitives(geometry, Polygon.class)); + case TRIANGULATED_SURFACE: + return TriangulatedSurface.of(getPrimitives(geometry, Polygon.class)); + case COMPOSITE_SURFACE: + return CompositeSurface.of(getPrimitives(geometry, Polygon.class)); + case SOLID: + return Solid.of(CompositeSurface.of(getPrimitives(geometry, Polygon.class))); + case MULTI_SOLID: + return MultiSolid.of(Solid.of(CompositeSurface.of(getPrimitives(geometry, Polygon.class)))); + case COMPOSITE_SOLID: + return CompositeSolid.of(Solid.of(CompositeSurface.of(getPrimitives(geometry, Polygon.class)))); + default: + return geometry; } - - return parent; - } else { - return null; + } catch (Exception e) { + throw new GeometryException("Failed to convert database geometry into a " + + type.getTypeName() + " geometry."); } } @@ -185,30 +210,38 @@ private List> getPrimitives(Geometry geometry) { } } - private Map buildHierarchy(JSONObject properties) { - JSONArray items = properties.getJSONArray(Properties.JSON_KEY_HIERARCHY); - if (items != null && !items.isEmpty()) { - Map hierarchy = new TreeMap<>(); - for (int i = 0; i < items.size(); i++) { - Object item = items.get(i); - if (item instanceof JSONObject) { - hierarchy.put(i, (JSONObject) item); - } - } - - return hierarchy; + @SuppressWarnings("unchecked") + private > List getPrimitives(Geometry geometry, Class type) { + List> primitives = getPrimitives(geometry); + if (!primitives.isEmpty() && type.isInstance(primitives.get(0))) { + return (List) primitives; + } else { + throw new ClassCastException(); } - - return Collections.emptyMap(); } - private void processMetadata(Geometry geometry, JSONObject properties) { - if (geometry.getObjectId().isEmpty()) { - geometry.setObjectId(properties.getString(Properties.JSON_KEY_OBJECT_ID)); + private > T getPrimitive(JSONObject item, List> primitives, Class type) throws GeometryException { + Integer index = item.getInteger(Properties.JSON_KEY_GEOMETRY_INDEX); + if (index == null) { + throw new GeometryException("Missing geometry index."); + } else if (index < 0 || index >= primitives.size()) { + throw new GeometryException("Geometry index out of bounds."); } - if (properties.getBooleanValue(Properties.JSON_KEY_IS_2D, false)) { - geometry.force2D(); + Geometry primitive = primitives.get(index); + if (type.isInstance(primitive)) { + return type.cast(primitive); + } else { + throw new GeometryException("Invalid primitive type " + primitive.getGeometryType() + "."); + } + } + + private GeometryType getType(JSONObject item) throws GeometryException { + GeometryType type = GeometryType.fromDatabaseValue(item.getIntValue(Properties.JSON_KEY_TYPE, -1)); + if (type != null) { + return type; + } else { + throw new GeometryException("Missing geometry type property."); } } } diff --git a/citydb-database/src/main/java/org/citydb/database/geometry/Properties.java b/citydb-database/src/main/java/org/citydb/database/geometry/Properties.java index e3899af7..708acda1 100644 --- a/citydb-database/src/main/java/org/citydb/database/geometry/Properties.java +++ b/citydb-database/src/main/java/org/citydb/database/geometry/Properties.java @@ -24,7 +24,7 @@ public class Properties { public static final String JSON_KEY_OBJECT_ID = "objectId"; public static final String JSON_KEY_IS_2D = "is2D"; - public static final String JSON_KEY_HIERARCHY = "hierarchy"; + public static final String JSON_KEY_CHILDREN = "children"; public static final String JSON_KEY_TYPE = "type"; public static final String JSON_KEY_PARENT = "parent"; public static final String JSON_KEY_GEOMETRY_INDEX = "geometryIndex"; diff --git a/citydb-database/src/main/java/org/citydb/database/geometry/PropertiesBuilder.java b/citydb-database/src/main/java/org/citydb/database/geometry/PropertiesBuilder.java index 729c024f..9c1ebfcd 100644 --- a/citydb-database/src/main/java/org/citydb/database/geometry/PropertiesBuilder.java +++ b/citydb-database/src/main/java/org/citydb/database/geometry/PropertiesBuilder.java @@ -29,21 +29,23 @@ public class PropertiesBuilder { public JSONObject buildProperties(Geometry geometry) { if (geometry != null) { - JSONObject properties = new JSONObject(); - - properties.put(Properties.JSON_KEY_OBJECT_ID, geometry.getOrCreateObjectId()); - if (geometry.getVertexDimension() == 2) { - properties.put(Properties.JSON_KEY_IS_2D, true); - } - Hierarchy hierarchy = new Hierarchy(); switch (geometry.getGeometryType()) { + case POINT: + buildHierarchy((Point) geometry, -1, hierarchy); + break; case MULTI_POINT: buildHierarchy((MultiPoint) geometry, hierarchy); break; + case LINE_STRING: + buildHierarchy((LineString) geometry, -1, hierarchy); + break; case MULTI_LINE_STRING: buildHierarchy((MultiLineString) geometry, hierarchy); break; + case POLYGON: + buildHierarchy((Polygon) geometry, -1, hierarchy); + break; case MULTI_SURFACE: case TRIANGULATED_SURFACE: case COMPOSITE_SURFACE: @@ -58,13 +60,7 @@ public JSONObject buildProperties(Geometry geometry) { break; } - if (!hierarchy.isEmpty()) { - properties.put(Properties.JSON_KEY_HIERARCHY, hierarchy.getContent()); - } - - if (!properties.isEmpty()) { - return properties; - } + return hierarchy.getRoot(); } return null; @@ -108,38 +104,45 @@ private void buildHierarchy(SolidCollection solids, Hierarchy hierarchy) { } private static class Hierarchy { - private final JSONArray content = new JSONArray(); + private final JSONObject root = new JSONObject(); + private final JSONArray children = new JSONArray(); private int geometryIndex; - private JSONArray getContent() { - return content; + private JSONObject getRoot() { + if (!children.isEmpty()) { + root.put(Properties.JSON_KEY_CHILDREN, children); + } + + return root; } private int add(Geometry geometry, int parent) { - JSONObject item = content.addObject() + JSONObject item = (parent == -1 ? root : children.addObject()) .fluentPut(Properties.JSON_KEY_TYPE, geometry.getGeometryType().getDatabaseValue()) .fluentPut(Properties.JSON_KEY_OBJECT_ID, geometry.getOrCreateObjectId()); - if (parent >= 0) { - item.put(Properties.JSON_KEY_PARENT, parent); - } - - switch (geometry.getGeometryType()) { - case POINT: - case LINE_STRING: - case POLYGON: - item.put(Properties.JSON_KEY_GEOMETRY_INDEX, geometryIndex++); + if (parent == -1) { + if (geometry.getVertexDimension() == 2) { + item.put(Properties.JSON_KEY_IS_2D, true); + } + } else { + if (parent > 0) { + item.put(Properties.JSON_KEY_PARENT, parent - 1); + } + + switch (geometry.getGeometryType()) { + case POINT: + case LINE_STRING: + case POLYGON: + item.put(Properties.JSON_KEY_GEOMETRY_INDEX, geometryIndex++); + } } if (geometry instanceof Polygon && ((Polygon) geometry).isReversed()) { item.put(Properties.JSON_KEY_IS_REVERSED, true); } - return content.size() - 1; - } - - private boolean isEmpty() { - return content.isEmpty(); + return children.size(); } } } diff --git a/resources/3dcitydb/json-schema/geometryProperties.schema.json b/resources/3dcitydb/json-schema/geometryProperties.schema.json index e8b1ac9c..560b07dc 100644 --- a/resources/3dcitydb/json-schema/geometryProperties.schema.json +++ b/resources/3dcitydb/json-schema/geometryProperties.schema.json @@ -5,6 +5,11 @@ "description": "Schema for the JSON column geometry_properties of the geometry_data table.", "type": "object", "properties": { + "type": { + "type": "number", + "minimum": 1, + "maximum": 11 + }, "objectId": { "type": "string" }, @@ -12,7 +17,7 @@ "type": "boolean", "default": false }, - "hierarchy": { + "children": { "type": "array", "items": { "type": "object", @@ -36,10 +41,16 @@ "default": false } }, + "required": [ + "type" + ], "additionalProperties": false }, "additionalItems": false } }, + "required": [ + "type" + ], "additionalProperties": false } \ No newline at end of file