Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions xtraplatform-features-oracle/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ dependencies {
provided project(':xtraplatform-crs')
provided project(':xtraplatform-features')
provided project(':xtraplatform-features-sql')
provided project(':xtraplatform-geometries')
}

moduleInfo {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import de.ii.xtraplatform.features.sql.domain.SqlQueryBatch;
import de.ii.xtraplatform.features.sql.domain.SqlQueryOptions;
import de.ii.xtraplatform.features.sql.domain.SqlRow;
import de.ii.xtraplatform.geometries.domain.transcode.wktwkb.WkbDialect;
import de.ii.xtraplatform.services.domain.Scheduler;
import de.ii.xtraplatform.streams.domain.Reactive;
import de.ii.xtraplatform.values.domain.ValueStore;
Expand Down Expand Up @@ -165,4 +166,9 @@ protected FeatureProviderConnector<SqlRow, SqlQueryBatch, SqlQueryOptions> creat
String providerSubType, String connectorId) {
return super.createConnector(FeatureProviderSql.PROVIDER_SUB_TYPE, connectorId);
}

@Override
protected WkbDialect getWkbDialect() {
return WkbDialect.ORACLE;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,21 @@ public class SqlDialectOras implements SqlDialect {

@Override
public String applyToWkt(String column, boolean forcePolygonCCW, boolean linearizeCurves) {
if (!forcePolygonCCW) {
return String.format("SDO_UTIL.TO_WKTGEOMETRY(%s)", column);
StringBuilder queryBuilder = new StringBuilder("SDO_UTIL.TO_WKTGEOMETRY(");
if (linearizeCurves) {
queryBuilder.append("SDO_GEOM.SDO_ARC_DENSIFY(");
}
return String.format("SDO_UTIL.TO_WKTGEOMETRY(SDO_UTIL.RECTIFY_GEOMETRY(%s, 0.001))", column);
if (forcePolygonCCW) {
queryBuilder.append("SDO_UTIL.RECTIFY_GEOMETRY(");
}
queryBuilder.append(column);
if (forcePolygonCCW) {
queryBuilder.append(",0.001)");
}
if (linearizeCurves) {
queryBuilder.append(",0.001,'arc_tolerance=0.1')");
}
return queryBuilder.append(")").toString();
}

@Override
Expand All @@ -53,10 +64,21 @@ public String applyToWkt(String wkt, int srid) {

@Override
public String applyToWkb(String column, boolean forcePolygonCCW, boolean linearizeCurves) {
if (!forcePolygonCCW) {
return String.format("SDO_UTIL.TO_WKBGEOMETRY(%s)", column);
StringBuilder queryBuilder = new StringBuilder("SDO_UTIL.TO_WKBGEOMETRY(");
if (linearizeCurves) {
queryBuilder.append("SDO_GEOM.SDO_ARC_DENSIFY(");
}
if (forcePolygonCCW) {
queryBuilder.append("SDO_UTIL.RECTIFY_GEOMETRY(");
}
queryBuilder.append(column);
if (forcePolygonCCW) {
queryBuilder.append(",0.001)");
}
if (linearizeCurves) {
queryBuilder.append(",0.001,'arc_tolerance=0.1')");
}
return String.format("SDO_UTIL.TO_WKBGEOMETRY(SDO_UTIL.RECTIFY_GEOMETRY(%s, 0.001))", column);
return queryBuilder.append(")").toString();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import de.ii.xtraplatform.geometries.domain.Geometry;
import de.ii.xtraplatform.geometries.domain.transcode.wktwkb.GeometryDecoderWkb;
import de.ii.xtraplatform.geometries.domain.transcode.wktwkb.GeometryDecoderWkt;
import de.ii.xtraplatform.geometries.domain.transcode.wktwkb.WkbDialect;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
Expand All @@ -51,6 +52,7 @@ public class FeatureDecoderSql
private final Map<String, DecoderFactory> subDecoderFactories;
private final Map<String, Decoder> subDecoders;
private final boolean geometryAsWkb;
private final WkbDialect wkbDialect;

private boolean started;
private boolean featureStarted;
Expand All @@ -68,10 +70,12 @@ public FeatureDecoderSql(
List<SqlQueryMapping> sqlQueryMappings,
Query query,
Map<String, DecoderFactory> subDecoderFactories,
boolean geometryAsWkb) {
boolean geometryAsWkb,
WkbDialect wkbDialect) {
this.mappings = mappings;
this.query = query;
this.geometryAsWkb = geometryAsWkb;
this.wkbDialect = wkbDialect;

this.mainTablePaths =
sqlQueryMappings.stream().map(s -> s.getMainTable().getFullPath()).toList();
Expand All @@ -94,7 +98,7 @@ public FeatureDecoderSql(
protected void init() {
this.context = createContext().setMappings(mappings).setQuery(query);
if (geometryAsWkb) {
this.geometryDecoderWkb = new GeometryDecoderWkb();
this.geometryDecoderWkb = new GeometryDecoderWkb(wkbDialect);
} else {
this.geometryDecoderWkt = new GeometryDecoderWkt();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
import de.ii.xtraplatform.features.sql.domain.FeatureProviderSqlData.QueryGeneratorSettings;
import de.ii.xtraplatform.features.sql.domain.SqlQueryColumn.Operation;
import de.ii.xtraplatform.features.sql.infra.db.SourceSchemaValidatorSql;
import de.ii.xtraplatform.geometries.domain.transcode.wktwkb.WkbDialect;
import de.ii.xtraplatform.services.domain.Scheduler;
import de.ii.xtraplatform.streams.domain.Reactive;
import de.ii.xtraplatform.streams.domain.Reactive.RunnableStream;
Expand Down Expand Up @@ -863,39 +864,43 @@ protected String applySourcePathDefaults(String path, boolean isValue) {
protected FeatureTokenDecoder<
SqlRow, FeatureSchema, SchemaMapping, ModifiableContext<FeatureSchema, SchemaMapping>>
getDecoder(Query query, Map<String, SchemaMapping> mappings) {
if (query instanceof FeatureQuery) {
FeatureQuery featureQuery = (FeatureQuery) query;

if (query instanceof FeatureQuery featureQuery) {
List<SqlQueryMapping> sqlQueryMappings = queryMappings.get(featureQuery.getType());

return new FeatureDecoderSql(
mappings,
sqlQueryMappings,
query,
subdecoders,
getData().getQueryGeneration().getGeometryAsWkb());
return createDecoder(query, mappings, sqlQueryMappings);
}

if (query instanceof MultiFeatureQuery) {
MultiFeatureQuery multiFeatureQuery = (MultiFeatureQuery) query;

if (query instanceof MultiFeatureQuery multiFeatureQuery) {
List<SqlQueryMapping> sqlQueryMappings =
multiFeatureQuery.getQueries().stream()
.flatMap(typeQuery -> queryMappings.get(typeQuery.getType()).stream())
.collect(Collectors.toList());
;

return new FeatureDecoderSql(
mappings,
sqlQueryMappings,
query,
subdecoders,
getData().getQueryGeneration().getGeometryAsWkb());

return createDecoder(query, mappings, sqlQueryMappings);
}

throw new IllegalArgumentException();
}

private FeatureTokenDecoder<
SqlRow, FeatureSchema, SchemaMapping, ModifiableContext<FeatureSchema, SchemaMapping>>
createDecoder(
Query query,
Map<String, SchemaMapping> mappings,
List<SqlQueryMapping> sqlQueryMappings) {
return new FeatureDecoderSql(
mappings,
sqlQueryMappings,
query,
subdecoders,
getData().getQueryGeneration().getGeometryAsWkb(),
getWkbDialect());
}

protected WkbDialect getWkbDialect() {
return WkbDialect.SQL_MM;
}

@Override
protected List<FeatureTokenTransformer> getDecoderTransformers() {
return ImmutableList.of(); // new FeatureTokenTransformerSorting());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class FeatureDecoderSqlSpec extends Specification {
ImmutableFeatureQuery.builder()
.type("biotop")
.build(),
false,
false)
singleDecoder = new FeatureDecoderSql(
SqlRowFixtures.TYPE_INFOS, ImmutableList.of(),
Expand All @@ -60,6 +61,7 @@ class FeatureDecoderSqlSpec extends Specification {
.type("biotop")
.returnsSingleFeature(true)
.build(),
false,
false)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ public enum Axes {
XYM(3, " M", 2000),
XYZM(4, " ZM", 3000);

public static final int SPECIAL_SHIFT = 1000000;

private final int size;
private final String wktSuffix;
private final int wkbShift;
Expand All @@ -32,6 +34,11 @@ public String getWktSuffix() {
}

public static Axes fromWkbCode(long geometryTypeCode) {
if (geometryTypeCode >= SPECIAL_SHIFT) {
// In Oracle, CIRCULARSTRING, COMPOUNDCURVE, CURVEPOLYGON, MULTICURVE, and MULTISURFACE
// with axes XY have a different geometry type code 100000x.
return Axes.XY;
}
return (geometryTypeCode >= Axes.XYZM.wkbShift)
? Axes.XYZM
: (geometryTypeCode >= Axes.XYM.wkbShift)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,42 @@

public class GeometryDecoderWkb extends AbstractGeometryDecoder {

private final WkbDialect dialect;

public GeometryDecoderWkb() {
this.dialect = WkbDialect.SQL_MM;
}

// Oracle WKB differs in two aspects from standard WKB:
// 1) CIRCULARSTRING, COMPOUNDCURVE, CURVEPOLYGON, MULTICURVE, and MULTISURFACE have a different
// geometry type code.
// 2) The embedded geometries do not repeat the endian byte for COMPOUNDCURVE, CURVEPOLYGON,
// MULTICURVE, MULTISURFACE.
public GeometryDecoderWkb(WkbDialect wkbDialect) {
this.dialect = wkbDialect;
}

public Geometry<?> decode(byte[] wkb) throws IOException {
return decode(wkb, Optional.empty());
}

public Geometry<?> decode(byte[] wkb, Optional<EpsgCrs> crs) throws IOException {
try (DataInputStream dis = new DataInputStream(new ByteArrayInputStream(wkb))) {
return decode(dis, crs, Set.of(), null);
return decode(dis, crs, Set.of(), null, Optional.empty());
}
}

public Geometry<?> decode(
DataInputStream dis, Optional<EpsgCrs> crs, Set<GeometryType> allowedTypes, Axes allowedAxes)
DataInputStream dis,
Optional<EpsgCrs> crs,
Set<GeometryType> allowedTypes,
Axes allowedAxes,
Optional<Boolean> isLittleEndianRootGeometry)
throws IOException {
boolean isLittleEndian = (dis.readByte() == 1);
boolean isLittleEndian =
dialect == WkbDialect.ORACLE && isLittleEndianRootGeometry.isPresent()
? isLittleEndianRootGeometry.get()
: dis.readByte() == 1;
long typeCode = readUnsignedInt(dis, isLittleEndian);
Axes axes = Axes.fromWkbCode(typeCode);
GeometryType type = WktWkbGeometryType.fromWkbType((int) typeCode).toGeometryType();
Expand All @@ -53,24 +75,29 @@ public Geometry<?> decode(
return switch (type) {
case POINT -> point(readPosition(dis, isLittleEndian, axes), crs);
case MULTI_POINT -> multiPoint2(
readListOfGeometry(dis, crs, axes, isLittleEndian, Set.of(GeometryType.POINT)), crs);
readListOfGeometry(dis, crs, axes, isLittleEndian, Set.of(GeometryType.POINT), false),
crs);
case LINE_STRING -> lineString(readPositionList(dis, isLittleEndian, axes), crs);
case MULTI_LINE_STRING -> multiLineString2(
readListOfGeometry(dis, crs, axes, isLittleEndian, Set.of(GeometryType.LINE_STRING)),
readListOfGeometry(
dis, crs, axes, isLittleEndian, Set.of(GeometryType.LINE_STRING), false),
crs);
case POLYGON -> polygon(readListOfPositionList(dis, isLittleEndian, axes), crs);
case MULTI_POLYGON -> multiPolygon2(
readListOfGeometry(dis, crs, axes, isLittleEndian, Set.of(GeometryType.POLYGON)), crs);
readListOfGeometry(dis, crs, axes, isLittleEndian, Set.of(GeometryType.POLYGON), false),
crs);
case CIRCULAR_STRING -> circularString(readPositionList(dis, isLittleEndian, axes), crs);
case POLYHEDRAL_SURFACE -> polyhedralSurface2(
readListOfGeometry(dis, crs, axes, isLittleEndian, Set.of(GeometryType.POLYGON)), crs);
readListOfGeometry(dis, crs, axes, isLittleEndian, Set.of(GeometryType.POLYGON), false),
crs);
case COMPOUND_CURVE -> compoundCurve(
readListOfGeometry(
dis,
crs,
axes,
isLittleEndian,
Set.of(GeometryType.LINE_STRING, GeometryType.CIRCULAR_STRING)),
Set.of(GeometryType.LINE_STRING, GeometryType.CIRCULAR_STRING),
true),
crs);
case CURVE_POLYGON -> curvePolygon(
readListOfGeometry(
Expand All @@ -81,7 +108,8 @@ public Geometry<?> decode(
Set.of(
GeometryType.LINE_STRING,
GeometryType.CIRCULAR_STRING,
GeometryType.COMPOUND_CURVE)),
GeometryType.COMPOUND_CURVE),
true),
crs);
case MULTI_CURVE -> multiCurve(
readListOfGeometry(
Expand All @@ -92,15 +120,17 @@ public Geometry<?> decode(
Set.of(
GeometryType.LINE_STRING,
GeometryType.CIRCULAR_STRING,
GeometryType.COMPOUND_CURVE)),
GeometryType.COMPOUND_CURVE),
false),
crs);
case MULTI_SURFACE -> multiSurface(
readListOfGeometry(
dis,
crs,
axes,
isLittleEndian,
Set.of(GeometryType.POLYGON, GeometryType.CURVE_POLYGON)),
Set.of(GeometryType.POLYGON, GeometryType.CURVE_POLYGON),
false),
crs);
case GEOMETRY_COLLECTION -> geometryCollection(
readListOfGeometry(
Expand All @@ -115,7 +145,8 @@ public Geometry<?> decode(
GeometryType.MULTI_POINT,
GeometryType.MULTI_LINE_STRING,
GeometryType.MULTI_POLYGON,
GeometryType.GEOMETRY_COLLECTION)),
GeometryType.GEOMETRY_COLLECTION),
false),
crs);
default -> throw new IllegalStateException("Unsupported geometry type: " + type);
};
Expand Down Expand Up @@ -180,12 +211,21 @@ private List<Geometry<?>> readListOfGeometry(
Optional<EpsgCrs> crs,
Axes axes,
boolean isLittleEndian,
Set<GeometryType> allowedTypes)
Set<GeometryType> allowedTypes,
boolean embeddedGeometriesDoNotHaveLittleEndianFlagInOracle)
throws IOException {
long num = readUnsignedInt(dis, isLittleEndian);
ImmutableList.Builder<Geometry<?>> builder = ImmutableList.builder();
for (int i = 0; i < num; i++) {
Geometry<?> g = decode(dis, crs, allowedTypes, axes);
Geometry<?> g =
decode(
dis,
crs,
allowedTypes,
axes,
dialect == WkbDialect.ORACLE && embeddedGeometriesDoNotHaveLittleEndianFlagInOracle
? Optional.of(isLittleEndian)
: Optional.empty());
if (g != null) builder.add(g);
}
return builder.build();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* Copyright 2025 interactive instruments GmbH
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package de.ii.xtraplatform.geometries.domain.transcode.wktwkb;

public enum WkbDialect {
SQL_MM,
ORACLE;
}
Loading