提交 000f7bb6 authored 作者: Evgenij Ryazanov's avatar Evgenij Ryazanov

Cache dimension system and envelope in ValueGeometry

上级 2ef109c3
...@@ -69,7 +69,7 @@ class AggregateDataEnvelope extends AggregateData { ...@@ -69,7 +69,7 @@ class AggregateDataEnvelope extends AggregateData {
if (envelope == null) { if (envelope == null) {
return ValueNull.INSTANCE; return ValueNull.INSTANCE;
} }
return ValueGeometry.get(GeometryUtils.envelope2wkb(envelope)); return ValueGeometry.fromEnvelope(envelope);
} }
} }
...@@ -34,7 +34,6 @@ import org.h2.result.SearchRow; ...@@ -34,7 +34,6 @@ import org.h2.result.SearchRow;
import org.h2.result.SortOrder; import org.h2.result.SortOrder;
import org.h2.table.IndexColumn; import org.h2.table.IndexColumn;
import org.h2.table.TableFilter; import org.h2.table.TableFilter;
import org.h2.util.geometry.GeometryUtils;
import org.h2.value.Value; import org.h2.value.Value;
import org.h2.value.ValueGeometry; import org.h2.value.ValueGeometry;
import org.h2.value.ValueLong; import org.h2.value.ValueLong;
...@@ -268,7 +267,7 @@ public class MVSpatialIndex extends BaseIndex implements SpatialIndex, MVIndex { ...@@ -268,7 +267,7 @@ public class MVSpatialIndex extends BaseIndex implements SpatialIndex, MVIndex {
bmaxyf = maxyf; bmaxyf = maxyf;
} }
} }
return ValueGeometry.get(GeometryUtils.envelope2wkb(new double[] {bminxf, bmaxxf, bminyf, bmaxyf})); return ValueGeometry.fromEnvelope(new double[] {bminxf, bmaxxf, bminyf, bmaxyf});
} }
return ValueNull.INSTANCE; return ValueNull.INSTANCE;
} }
...@@ -514,8 +513,7 @@ public class MVSpatialIndex extends BaseIndex implements SpatialIndex, MVIndex { ...@@ -514,8 +513,7 @@ public class MVSpatialIndex extends BaseIndex implements SpatialIndex, MVIndex {
} }
Value getBounds() { Value getBounds() {
return hasBounds ? ValueGeometry.get( return hasBounds ? ValueGeometry.fromEnvelope(new double[] {bminxd, bmaxxd, bminyd, bmaxyd})
GeometryUtils.envelope2wkb(new double[] {bminxd, bmaxxd, bminyd, bmaxyd}))
: ValueNull.INSTANCE; : ValueNull.INSTANCE;
} }
......
...@@ -237,11 +237,26 @@ public final class EWKBUtils { ...@@ -237,11 +237,26 @@ public final class EWKBUtils {
public static byte[] ewkb2ewkb(byte[] ewkb) { public static byte[] ewkb2ewkb(byte[] ewkb) {
// Determine dimension system first // Determine dimension system first
DimensionSystemTarget dimensionTarget = new DimensionSystemTarget(); DimensionSystemTarget dimensionTarget = new DimensionSystemTarget();
parseEKWB(ewkb, dimensionTarget); parseEWKB(ewkb, dimensionTarget);
// Write an EWKB // Write an EWKB
return ewkb2ewkb(ewkb, dimensionTarget.getDimensionSystem());
}
/**
* Converts any supported EWKB to EWKB representation that is used by this
* class. Reduces dimension system to minimal possible and uses EWKB flags
* for dimension system indication. May also perform other changes.
*
* @param ewkb
* source EWKB
* @param dimension
* dimension system
* @return canonical EWKB, may be the same as the source
*/
public static byte[] ewkb2ewkb(byte[] ewkb, int dimensionSystem) {
ByteArrayOutputStream output = new ByteArrayOutputStream(); ByteArrayOutputStream output = new ByteArrayOutputStream();
EWKBTarget target = new EWKBTarget(output, dimensionTarget.getDimensionSystem()); EWKBTarget target = new EWKBTarget(output, dimensionSystem);
parseEKWB(ewkb, target); parseEWKB(ewkb, target);
return output.toByteArray(); return output.toByteArray();
} }
...@@ -253,8 +268,8 @@ public final class EWKBUtils { ...@@ -253,8 +268,8 @@ public final class EWKBUtils {
* @param target * @param target
* output target * output target
*/ */
public static void parseEKWB(byte[] ewkb, Target target) { public static void parseEWKB(byte[] ewkb, Target target) {
parseEKWB(new EWKBSource(ewkb), target, 0, 0); parseEWKB(new EWKBSource(ewkb), target, 0, 0);
} }
/** /**
...@@ -270,7 +285,7 @@ public final class EWKBUtils { ...@@ -270,7 +285,7 @@ public final class EWKBUtils {
* SRID of a parent geometry collection, or any value for the * SRID of a parent geometry collection, or any value for the
* root geometry (will be determined from the EWKB instead) * root geometry (will be determined from the EWKB instead)
*/ */
private static void parseEKWB(EWKBSource source, Target target, int parentType, int parentSrid) { private static void parseEWKB(EWKBSource source, Target target, int parentType, int parentSrid) {
try { try {
// Read byte order of a next geometry // Read byte order of a next geometry
switch (source.readByte()) { switch (source.readByte()) {
...@@ -374,7 +389,7 @@ public final class EWKBUtils { ...@@ -374,7 +389,7 @@ public final class EWKBUtils {
target.startCollection(type, srid, numItems); target.startCollection(type, srid, numItems);
for (int i = 0; i < numItems; i++) { for (int i = 0; i < numItems; i++) {
Target innerTarget = target.startCollectionItem(i, numItems); Target innerTarget = target.startCollectionItem(i, numItems);
parseEKWB(source, innerTarget, type, srid); parseEWKB(source, innerTarget, type, srid);
target.endCollectionItem(innerTarget, i, numItems); target.endCollectionItem(innerTarget, i, numItems);
} }
target.endCollection(type); target.endCollection(type);
......
...@@ -442,11 +442,24 @@ public final class EWKTUtils { ...@@ -442,11 +442,24 @@ public final class EWKTUtils {
public static String ewkb2ewkt(byte[] ewkb) { public static String ewkb2ewkt(byte[] ewkb) {
// Determine dimension system first // Determine dimension system first
DimensionSystemTarget dimensionTarget = new DimensionSystemTarget(); DimensionSystemTarget dimensionTarget = new DimensionSystemTarget();
EWKBUtils.parseEKWB(ewkb, dimensionTarget); EWKBUtils.parseEWKB(ewkb, dimensionTarget);
// Write an EWKT // Write an EWKT
return ewkb2ewkt(ewkb, dimensionTarget.getDimensionSystem());
}
/**
* Converts EWKB to EWKT.
*
* @param ewkb
* source EWKB
* @param dimension
* dimension system
* @return EWKT representation
*/
public static String ewkb2ewkt(byte[] ewkb, int dimensionSystem) {
StringBuilder output = new StringBuilder(); StringBuilder output = new StringBuilder();
EWKTTarget target = new EWKTTarget(output, dimensionTarget.getDimensionSystem()); EWKTTarget target = new EWKTTarget(output, dimensionSystem);
EWKBUtils.parseEKWB(ewkb, target); EWKBUtils.parseEWKB(ewkb, target);
return output.toString(); return output.toString();
} }
...@@ -462,8 +475,21 @@ public final class EWKTUtils { ...@@ -462,8 +475,21 @@ public final class EWKTUtils {
DimensionSystemTarget dimensionTarget = new DimensionSystemTarget(); DimensionSystemTarget dimensionTarget = new DimensionSystemTarget();
parseEWKT(ewkt, dimensionTarget); parseEWKT(ewkt, dimensionTarget);
// Write an EWKB // Write an EWKB
return ewkt2ewkb(ewkt, dimensionTarget.getDimensionSystem());
}
/**
* Converts EWKT to EWKB.
*
* @param ewkt
* source EWKT
* @param dimension
* dimension system
* @return EWKB representation
*/
public static byte[] ewkt2ewkb(String ewkt, int dimensionSystem) {
ByteArrayOutputStream output = new ByteArrayOutputStream(); ByteArrayOutputStream output = new ByteArrayOutputStream();
EWKBTarget target = new EWKBTarget(output, dimensionTarget.getDimensionSystem()); EWKBTarget target = new EWKBTarget(output, dimensionSystem);
parseEWKT(ewkt, target); parseEWKT(ewkt, target);
return output.toByteArray(); return output.toByteArray();
} }
......
...@@ -468,7 +468,7 @@ public final class GeometryUtils { ...@@ -468,7 +468,7 @@ public final class GeometryUtils {
*/ */
public static double[] getEnvelope(byte[] ewkb) { public static double[] getEnvelope(byte[] ewkb) {
EnvelopeAndDimensionSystemTarget target = new EnvelopeAndDimensionSystemTarget(); EnvelopeAndDimensionSystemTarget target = new EnvelopeAndDimensionSystemTarget();
EWKBUtils.parseEKWB(ewkb, target); EWKBUtils.parseEWKB(ewkb, target);
return target.getEnvelope(); return target.getEnvelope();
} }
......
...@@ -198,10 +198,23 @@ public final class JTSUtils { ...@@ -198,10 +198,23 @@ public final class JTSUtils {
public static Geometry ewkb2geometry(byte[] ewkb) { public static Geometry ewkb2geometry(byte[] ewkb) {
// Determine dimension system first // Determine dimension system first
DimensionSystemTarget dimensionTarget = new DimensionSystemTarget(); DimensionSystemTarget dimensionTarget = new DimensionSystemTarget();
EWKBUtils.parseEKWB(ewkb, dimensionTarget); EWKBUtils.parseEWKB(ewkb, dimensionTarget);
// Generate a Geometry // Generate a Geometry
GeometryTarget target = new GeometryTarget(dimensionTarget.getDimensionSystem()); return ewkb2geometry(ewkb, dimensionTarget.getDimensionSystem());
EWKBUtils.parseEKWB(ewkb, target); }
/**
* Converts EWKB to a JTS geometry object.
*
* @param ewkb
* source EWKB
* @param dimensionSystem
* dimension system
* @return JTS geometry object
*/
public static Geometry ewkb2geometry(byte[] ewkb, int dimensionSystem) {
GeometryTarget target = new GeometryTarget(dimensionSystem);
EWKBUtils.parseEWKB(ewkb, target);
return target.getGeometry(); return target.getGeometry();
} }
...@@ -216,9 +229,23 @@ public final class JTSUtils { ...@@ -216,9 +229,23 @@ public final class JTSUtils {
// Determine dimension system first // Determine dimension system first
DimensionSystemTarget dimensionTarget = new DimensionSystemTarget(); DimensionSystemTarget dimensionTarget = new DimensionSystemTarget();
parseGeometry(geometry, dimensionTarget); parseGeometry(geometry, dimensionTarget);
// Write an EWKB
return geometry2ewkb(geometry, dimensionTarget.getDimensionSystem());
}
/**
* Converts Geometry to EWKB.
*
* @param geometry
* source geometry
* @param dimensionSystem
* dimension system
* @return EWKB representation
*/
public static byte[] geometry2ewkb(Geometry geometry, int dimensionSystem) {
// Write an EWKB // Write an EWKB
ByteArrayOutputStream output = new ByteArrayOutputStream(); ByteArrayOutputStream output = new ByteArrayOutputStream();
EWKBTarget target = new EWKBTarget(output, dimensionTarget.getDimensionSystem()); EWKBTarget target = new EWKBTarget(output, dimensionSystem);
parseGeometry(geometry, target); parseGeometry(geometry, target);
return output.toByteArray(); return output.toByteArray();
} }
...@@ -231,7 +258,7 @@ public final class JTSUtils { ...@@ -231,7 +258,7 @@ public final class JTSUtils {
* @param target * @param target
* output target * output target
*/ */
private static void parseGeometry(Geometry geometry, Target target) { public static void parseGeometry(Geometry geometry, Target target) {
parseGeometry(geometry, target, 0, 0); parseGeometry(geometry, target, 0, 0);
} }
......
...@@ -1215,7 +1215,7 @@ public abstract class Value { ...@@ -1215,7 +1215,7 @@ public abstract class Value {
private ValueGeometry convertToGeometry() { private ValueGeometry convertToGeometry() {
switch (getType()) { switch (getType()) {
case BYTES: case BYTES:
return ValueGeometry.get(getBytesNoCopy()); return ValueGeometry.getFromEWKB(getBytesNoCopy());
case JAVA_OBJECT: case JAVA_OBJECT:
Object object = JdbcUtils.deserialize(getBytesNoCopy(), getDataHandler()); Object object = JdbcUtils.deserialize(getBytesNoCopy(), getDataHandler());
if (DataType.isGeometry(object)) { if (DataType.isGeometry(object)) {
......
...@@ -13,8 +13,10 @@ import org.h2.message.DbException; ...@@ -13,8 +13,10 @@ import org.h2.message.DbException;
import org.h2.util.Bits; import org.h2.util.Bits;
import org.h2.util.StringUtils; import org.h2.util.StringUtils;
import org.h2.util.Utils; import org.h2.util.Utils;
import org.h2.util.geometry.EWKBUtils;
import org.h2.util.geometry.EWKTUtils; import org.h2.util.geometry.EWKTUtils;
import org.h2.util.geometry.GeometryUtils; import org.h2.util.geometry.GeometryUtils;
import org.h2.util.geometry.GeometryUtils.EnvelopeAndDimensionSystemTarget;
import org.h2.util.geometry.JTSUtils; import org.h2.util.geometry.JTSUtils;
import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.Geometry;
...@@ -40,10 +42,9 @@ public class ValueGeometry extends Value { ...@@ -40,10 +42,9 @@ public class ValueGeometry extends Value {
private final int hashCode; private final int hashCode;
/** /**
* The value. Converted from WKB only on request as conversion from/to WKB * Dimension system. -1 if not known yet.
* cost a significant amount of CPU cycles.
*/ */
private Object geometry; private int dimensionSystem;
/** /**
* The envelope of the value. Calculated only on request. * The envelope of the value. Calculated only on request.
...@@ -51,13 +52,23 @@ public class ValueGeometry extends Value { ...@@ -51,13 +52,23 @@ public class ValueGeometry extends Value {
private double[] envelope; private double[] envelope;
/** /**
* Create a new geometry objects. * The value. Converted from WKB only on request as conversion from/to WKB
* cost a significant amount of CPU cycles.
*/
private Object geometry;
/**
* Create a new geometry object.
* *
* @param bytes the EWKB bytes * @param bytes the EWKB bytes
* @param dimensionSystem dimension system
* @param envelope the envelope
*/ */
private ValueGeometry(byte[] bytes) { private ValueGeometry(byte[] bytes, int dimensionSystem, double[] envelope) {
this.bytes = bytes; this.bytes = bytes;
this.hashCode = Arrays.hashCode(bytes); this.hashCode = Arrays.hashCode(bytes);
this.dimensionSystem = dimensionSystem;
this.envelope = envelope;
} }
/** /**
...@@ -68,7 +79,16 @@ public class ValueGeometry extends Value { ...@@ -68,7 +79,16 @@ public class ValueGeometry extends Value {
* @return the value * @return the value
*/ */
public static ValueGeometry getFromGeometry(Object o) { public static ValueGeometry getFromGeometry(Object o) {
return get(JTSUtils.geometry2ewkb((Geometry) o)); try {
EnvelopeAndDimensionSystemTarget target = new EnvelopeAndDimensionSystemTarget();
Geometry g = (Geometry) o;
JTSUtils.parseGeometry(g, target);
int dimensionSystem = target.getDimensionSystem();
return (ValueGeometry) Value.cache(new ValueGeometry(JTSUtils.geometry2ewkb(g, dimensionSystem),
dimensionSystem, target.getEnvelope()));
} catch (RuntimeException ex) {
throw DbException.convert(ex);
}
} }
/** /**
...@@ -79,7 +99,11 @@ public class ValueGeometry extends Value { ...@@ -79,7 +99,11 @@ public class ValueGeometry extends Value {
*/ */
public static ValueGeometry get(String s) { public static ValueGeometry get(String s) {
try { try {
return get(EWKTUtils.ewkt2ewkb(s)); EnvelopeAndDimensionSystemTarget target = new EnvelopeAndDimensionSystemTarget();
EWKTUtils.parseEWKT(s, target);
int dimensionSystem = target.getDimensionSystem();
return (ValueGeometry) Value.cache(new ValueGeometry(EWKTUtils.ewkt2ewkb(s, dimensionSystem),
dimensionSystem, target.getEnvelope()));
} catch (RuntimeException ex) { } catch (RuntimeException ex) {
throw DbException.convert(ex); throw DbException.convert(ex);
} }
...@@ -98,13 +122,42 @@ public class ValueGeometry extends Value { ...@@ -98,13 +122,42 @@ public class ValueGeometry extends Value {
} }
/** /**
* Get or create a geometry value for the given geometry. * Get or create a geometry value for the given internal EWKB representation.
* *
* @param bytes the WKB representation of the geometry * @param bytes the WKB representation of the geometry. May not be modified.
* @return the value * @return the value
*/ */
public static ValueGeometry get(byte[] bytes) { public static ValueGeometry get(byte[] bytes) {
return (ValueGeometry) Value.cache(new ValueGeometry(bytes)); return (ValueGeometry) Value.cache(new ValueGeometry(bytes, -1, null));
}
/**
* Get or create a geometry value for the given EWKB value.
*
* @param bytes the WKB representation of the geometry
* @return the value
*/
public static ValueGeometry getFromEWKB(byte[] bytes) {
try {
EnvelopeAndDimensionSystemTarget target = new EnvelopeAndDimensionSystemTarget();
EWKBUtils.parseEWKB(bytes, target);
int dimensionSystem = target.getDimensionSystem();
return (ValueGeometry) Value.cache(new ValueGeometry(EWKBUtils.ewkb2ewkb(bytes, dimensionSystem),
dimensionSystem, target.getEnvelope()));
} catch (RuntimeException ex) {
throw DbException.convert(ex);
}
}
/**
* Creates a geometry value for the given envelope.
*
* @param envelope envelope. May not be modified.
* @return the value
*/
public static ValueGeometry fromEnvelope(double[] envelope) {
return (ValueGeometry) Value.cache(new ValueGeometry(GeometryUtils.envelope2wkb(envelope),
GeometryUtils.DIMENSION_SYSTEM_XY, envelope));
} }
/** /**
...@@ -116,7 +169,7 @@ public class ValueGeometry extends Value { ...@@ -116,7 +169,7 @@ public class ValueGeometry extends Value {
public Object getGeometry() { public Object getGeometry() {
if (geometry == null) { if (geometry == null) {
try { try {
geometry = JTSUtils.ewkb2geometry(bytes); geometry = JTSUtils.ewkb2geometry(bytes, getDimensionSystem());
} catch (RuntimeException ex) { } catch (RuntimeException ex) {
throw DbException.convert(ex); throw DbException.convert(ex);
} }
...@@ -153,15 +206,32 @@ public class ValueGeometry extends Value { ...@@ -153,15 +206,32 @@ public class ValueGeometry extends Value {
return 0; return 0;
} }
private void calculateInfo() {
if (dimensionSystem < 0) {
EnvelopeAndDimensionSystemTarget target = new EnvelopeAndDimensionSystemTarget();
EWKBUtils.parseEWKB(bytes, target);
envelope = target.getEnvelope();
dimensionSystem = target.getDimensionSystem();
}
}
/**
* Return a minimal dimension system that can be used for this geometry.
*
* @return dimension system
*/
public int getDimensionSystem() {
calculateInfo();
return dimensionSystem;
}
/** /**
* Return an envelope of this geometry. Do not modify the returned value. * Return an envelope of this geometry. Do not modify the returned value.
* *
* @return envelope of this geometry * @return envelope of this geometry
*/ */
public double[] getEnvelopeNoCopy() { public double[] getEnvelopeNoCopy() {
if (envelope == null) { calculateInfo();
envelope = GeometryUtils.getEnvelope(bytes);
}
return envelope; return envelope;
} }
...@@ -183,7 +253,7 @@ public class ValueGeometry extends Value { ...@@ -183,7 +253,7 @@ public class ValueGeometry extends Value {
* @return the union of this geometry envelope and another geometry envelope * @return the union of this geometry envelope and another geometry envelope
*/ */
public Value getEnvelopeUnion(ValueGeometry r) { public Value getEnvelopeUnion(ValueGeometry r) {
return get(GeometryUtils.envelope2wkb(GeometryUtils.union(getEnvelopeNoCopy(), r.getEnvelopeNoCopy()))); return fromEnvelope(GeometryUtils.union(getEnvelopeNoCopy(), r.getEnvelopeNoCopy()));
} }
@Override @Override
...@@ -247,12 +317,12 @@ public class ValueGeometry extends Value { ...@@ -247,12 +317,12 @@ public class ValueGeometry extends Value {
@Override @Override
public int getMemory() { public int getMemory() {
return getEWKB().length * 20 + 24; return bytes.length * 20 + 24;
} }
@Override @Override
public boolean equals(Object other) { public boolean equals(Object other) {
return other instanceof ValueGeometry && Arrays.equals(getEWKB(), ((ValueGeometry) other).getEWKB()); return other instanceof ValueGeometry && Arrays.equals(bytes, ((ValueGeometry) other).bytes);
} }
/** /**
...@@ -261,7 +331,7 @@ public class ValueGeometry extends Value { ...@@ -261,7 +331,7 @@ public class ValueGeometry extends Value {
* @return the extended well-known text * @return the extended well-known text
*/ */
public String getEWKT() { public String getEWKT() {
return EWKTUtils.ewkb2ewkt(bytes); return EWKTUtils.ewkb2ewkt(bytes, getDimensionSystem());
} }
/** /**
......
...@@ -156,7 +156,7 @@ public class TestGeometryUtils extends TestBase { ...@@ -156,7 +156,7 @@ public class TestGeometryUtils extends TestBase {
Envelope envelopeFromJTS = geometryFromJTS.getEnvelopeInternal(); Envelope envelopeFromJTS = geometryFromJTS.getEnvelopeInternal();
testEnvelope(envelopeFromJTS, GeometryUtils.getEnvelope(wkbFromJTS)); testEnvelope(envelopeFromJTS, GeometryUtils.getEnvelope(wkbFromJTS));
EnvelopeAndDimensionSystemTarget target = new EnvelopeAndDimensionSystemTarget(); EnvelopeAndDimensionSystemTarget target = new EnvelopeAndDimensionSystemTarget();
EWKBUtils.parseEKWB(wkbFromJTS, target); EWKBUtils.parseEWKB(wkbFromJTS, target);
testEnvelope(envelopeFromJTS, target.getEnvelope()); testEnvelope(envelopeFromJTS, target.getEnvelope());
// Test dimensions // Test dimensions
...@@ -242,10 +242,10 @@ public class TestGeometryUtils extends TestBase { ...@@ -242,10 +242,10 @@ public class TestGeometryUtils extends TestBase {
private void testDimensions(int expected, byte[] ewkb) { private void testDimensions(int expected, byte[] ewkb) {
DimensionSystemTarget dst = new DimensionSystemTarget(); DimensionSystemTarget dst = new DimensionSystemTarget();
EWKBUtils.parseEKWB(ewkb, dst); EWKBUtils.parseEWKB(ewkb, dst);
assertEquals(expected, dst.getDimensionSystem()); assertEquals(expected, dst.getDimensionSystem());
EnvelopeAndDimensionSystemTarget envelopeAndDimensionTarget = new EnvelopeAndDimensionSystemTarget(); EnvelopeAndDimensionSystemTarget envelopeAndDimensionTarget = new EnvelopeAndDimensionSystemTarget();
EWKBUtils.parseEKWB(ewkb, envelopeAndDimensionTarget); EWKBUtils.parseEWKB(ewkb, envelopeAndDimensionTarget);
assertEquals(expected, envelopeAndDimensionTarget.getDimensionSystem()); assertEquals(expected, envelopeAndDimensionTarget.getDimensionSystem());
} }
......
...@@ -793,4 +793,4 @@ immediate hhmmss scheduled hhmm prematurely postponed arranges subexpression sub ...@@ -793,4 +793,4 @@ immediate hhmmss scheduled hhmm prematurely postponed arranges subexpression sub
minxf maxxf minyf maxyf bminxf bmaxxf bminyf bmaxyf minxf maxxf minyf maxyf bminxf bmaxxf bminyf bmaxyf
minxd maxxd minyd maxyd bminxd bmaxxd bminyd bmaxyd minxd maxxd minyd maxyd bminxd bmaxxd bminyd bmaxyd
interior envelopes multilinestring multipoint packed exterior normalization awkward determination subgeometries interior envelopes multilinestring multipoint packed exterior normalization awkward determination subgeometries
xym ekwb normalizes coord setz xyzm geometrycollection multipolygon mixup rings polygons xym normalizes coord setz xyzm geometrycollection multipolygon mixup rings polygons
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论