From a41452a4f708ef0232b716eb2c98712d07e4e7b1 Mon Sep 17 00:00:00 2001 From: Felix Date: Wed, 9 Oct 2024 07:54:22 +0200 Subject: [PATCH 1/8] Add basic class to triangulate polygons for preprocessing step --- java/build.gradle | 49 ++++--- .../triangulation/PolygonConverter.java | 123 ++++++++++++++++++ .../mlt/earcut/EarCutVectorizationTest.java | 64 +++++++++ 3 files changed, 216 insertions(+), 20 deletions(-) create mode 100644 java/src/main/java/com/mlt/converter/triangulation/PolygonConverter.java create mode 100644 java/src/test/java/com/mlt/earcut/EarCutVectorizationTest.java diff --git a/java/build.gradle b/java/build.gradle index a4574b50..357c1fd5 100644 --- a/java/build.gradle +++ b/java/build.gradle @@ -2,6 +2,7 @@ plugins { id 'java-library' id('com.diffplug.spotless') version '6.25.0' id 'me.champeau.jmh' version '0.7.2' + id 'jacoco' } repositories { @@ -20,13 +21,16 @@ dependencies { implementation 'me.lemire.integercompression:JavaFastPFOR:0.2.1' implementation 'com.google.guava:guava:33.2.1-jre' implementation 'commons-cli:commons-cli:1.8.0' - implementation 'org.slf4j:slf4j-simple:2.0.13' + implementation 'org.slf4j:slf4j-simple:2.0.16' implementation 'no.ecc.vectortile:java-vector-tile:1.3.23' implementation 'io.github.sebasbaumh:mapbox-vector-tile-java:23.1.0' implementation 'org.apache.orc:orc-core:1.8.1' implementation 'com.github.davidmoten:hilbert-curve:0.2.3' - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.2' - testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.2' + implementation 'com.carrotsearch:hppc:0.10.0' + implementation "io.github.earcut4j:earcut4j:2.2.2" + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.3' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.3' + testImplementation 'org.junit.jupiter:junit-jupiter-params:5.10.3' testImplementation 'org.openjdk.jmh:jmh-core:1.37 ' testImplementation 'org.openjdk.jmh:jmh-generator-annprocess:1.37' } @@ -34,11 +38,16 @@ dependencies { test { useJUnitPlatform() testLogging { - outputs.upToDateWhen {false} + outputs.upToDateWhen { false } showStandardStreams = true events "passed", "skipped", "failed" exceptionFormat "full" } + finalizedBy jacocoTestReport +} + +jacocoTestReport { + dependsOn test } java { @@ -53,30 +62,30 @@ spotless { target 'src/*/java/**/*.java' googleJavaFormat('1.15.0') removeUnusedImports() - } -} - -task compileWrapper(type: Exec) { - doFirst { - if (System.properties['os.name'].toLowerCase().contains('windows')) { - executable "./resources/compile-windows.bat" - } else { - executable "./resources/compile" - } } } +//task compileWrapper(type: Exec) { +// doFirst { +// if (System.properties['os.name'].toLowerCase().contains('windows')) { +// executable "./resources/compile-windows.bat" +// } else { +// executable "./resources/compile" +// } +// } +//} + gradle.projectsEvaluated { tasks.withType(JavaCompile) { options.compilerArgs << "-Xlint:unchecked" } } -compileJava.dependsOn compileWrapper +//compileJava.dependsOn compileWrapper ["encode", "decode", "meta"].each { name -> - task "$name" (type: Jar, dependsOn: compileJava) { + task "$name"(type: Jar, dependsOn: compileJava) { archiveFileName = name + ".jar" duplicatesStrategy = DuplicatesStrategy.EXCLUDE from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } } @@ -85,11 +94,11 @@ compileJava.dependsOn compileWrapper } } -encode.manifest.attributes 'Implementation-Title' : 'encode an mlt from an mvt', +encode.manifest.attributes 'Implementation-Title': 'encode an mlt from an mvt', 'Main-Class': 'com.mlt.tools.Encode' -decode.manifest.attributes 'Implementation-Title' : 'decode an mlt', +decode.manifest.attributes 'Implementation-Title': 'decode an mlt', 'Main-Class': 'com.mlt.tools.Decode' -meta.manifest.attributes 'Implementation-Title' : 'generate a mltmetadata.pbf file from an mvt', +meta.manifest.attributes 'Implementation-Title': 'generate a mltmetadata.pbf file from an mvt', 'Main-Class': 'com.mlt.tools.Meta' task cli { @@ -98,4 +107,4 @@ task cli { dependsOn encode dependsOn decode dependsOn meta -} \ No newline at end of file +} diff --git a/java/src/main/java/com/mlt/converter/triangulation/PolygonConverter.java b/java/src/main/java/com/mlt/converter/triangulation/PolygonConverter.java new file mode 100644 index 00000000..a4976b55 --- /dev/null +++ b/java/src/main/java/com/mlt/converter/triangulation/PolygonConverter.java @@ -0,0 +1,123 @@ +package com.mlt.converter.triangulation; + +import com.mlt.converter.encodings.EncodingUtils; +import com.mlt.converter.encodings.IntegerEncoder; +import com.mlt.converter.mvt.MapboxVectorTile; +import com.mlt.converter.mvt.MvtUtils; +import com.mlt.data.Feature; +import com.mlt.metadata.stream.LogicalStreamType; +import com.mlt.metadata.stream.OffsetType; +import com.mlt.metadata.stream.PhysicalLevelTechnique; +import com.mlt.metadata.stream.PhysicalStreamType; +import earcut4j.Earcut; +import org.apache.commons.lang3.ArrayUtils; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.MultiPolygon; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +public class PolygonConverter { + private final ArrayList numTrianglesPerPolygon = new ArrayList<>(); + + private final ArrayList indexBuffer = new ArrayList<>(); + + public PolygonConverter(Path vectorTilePath) throws IOException { + var vectorTile = MvtUtils.decodeMvt(vectorTilePath); + + this.triangulatePolygons(vectorTile); + } + + public List getNumTrianglesPerPolygon() { + return numTrianglesPerPolygon; + } + + public List getIndexBuffer() { + return indexBuffer; + } + + public byte[] getEncodedIndexBuffer() { + return IntegerEncoder.encodeIntStream(this.indexBuffer, PhysicalLevelTechnique.FAST_PFOR, false, PhysicalStreamType.DATA, new LogicalStreamType(OffsetType.INDEX)); + } + + public byte[] getEncodedNumberOfTrianglesPerPolygon() { + return IntegerEncoder.encodeIntStream(this.numTrianglesPerPolygon, PhysicalLevelTechnique.FAST_PFOR, false, PhysicalStreamType.DATA, new LogicalStreamType(OffsetType.INDEX)); + } + + public byte[] getGzippedIndexBuffer() throws IOException { + return EncodingUtils.gzip(this.getEncodedIndexBuffer()); + } + + public byte[] getGzippedNumberOfTrianglesPerPolygon() throws IOException { + return EncodingUtils.gzip(this.getEncodedNumberOfTrianglesPerPolygon()); + } + + private void triangulatePolygons(MapboxVectorTile vectorTile) { + vectorTile.layers().forEach(layer -> layer.features().forEach(this::triangulatePolygonFeature)); + } + + private void triangulatePolygonFeature(Feature feature) { + if (feature.geometry().getGeometryType().equals(Geometry.TYPENAME_POLYGON)) { + var coordinates = convertCoordinates(feature.geometry().getCoordinates()); + + List triangles = Earcut.earcut(coordinates, null, 2); + + indexBuffer.addAll(triangles); + numTrianglesPerPolygon.add(triangles.size() / 3); + } else if (feature.geometry().getGeometryType().equals(Geometry.TYPENAME_MULTIPOLYGON)) { + triangulatePolygonWithHoles(feature); + } + } + + private void triangulatePolygonWithHoles(Feature feature) { + var multipolygon = (MultiPolygon) feature.geometry(); + var holeIndex = 0; + + ArrayList multiPolygonCoordinates = new ArrayList<>(); + ArrayList holeIndices = new ArrayList<>(); + + for (int i = 0; i < multipolygon.getNumGeometries(); i++) { + // assertion: first polygon defines the outer linear ring and the other polygons its holes! + if (i == 0) { + holeIndex = multipolygon.getGeometryN(i).getCoordinates().length; + holeIndices.add(holeIndex); + } else if (i != multipolygon.getNumGeometries() - 1) { + holeIndex += multipolygon.getGeometryN(i).getCoordinates().length; + holeIndices.add(holeIndex); + } + + var coordinates = multipolygon.getGeometryN(i).getCoordinates(); + for (Coordinate coordinate : coordinates) { + multiPolygonCoordinates.add(coordinate.x); + multiPolygonCoordinates.add(coordinate.y); + if (!Double.isNaN(coordinate.z)) { + multiPolygonCoordinates.add(coordinate.z); + } + } + } + + var doubleArray = ArrayUtils.toPrimitive(multiPolygonCoordinates.toArray(new Double[0])); + List triangleVertices = Earcut.earcut(doubleArray, holeIndices.stream().mapToInt(Integer::intValue).toArray(), 2); + + indexBuffer.addAll(triangleVertices); + numTrianglesPerPolygon.add(triangleVertices.size() / 3); + } + + private double[] convertCoordinates(Coordinate[] coordinates) { + ArrayList convertedCoordinates = new ArrayList<>(); + + for (Coordinate coordinate : coordinates) { + convertedCoordinates.add(coordinate.x); + convertedCoordinates.add(coordinate.y); + if (!Double.isNaN(coordinate.z)) { + convertedCoordinates.add(coordinate.z); + } + } + Double[] array = convertedCoordinates.toArray(new Double[0]); + + return ArrayUtils.toPrimitive(array); + } +} diff --git a/java/src/test/java/com/mlt/earcut/EarCutVectorizationTest.java b/java/src/test/java/com/mlt/earcut/EarCutVectorizationTest.java new file mode 100644 index 00000000..4fbc2afe --- /dev/null +++ b/java/src/test/java/com/mlt/earcut/EarCutVectorizationTest.java @@ -0,0 +1,64 @@ +package com.mlt.earcut; + +import com.mlt.converter.triangulation.PolygonConverter; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.Paths; + + +class EarcutVectorizationTest { + @Test + void vectorizeVectorTile() throws IOException { + var path = Paths.get("../test/fixtures/amazon/5_5_11.pbf").toAbsolutePath(); + + var polygonConverter = new PolygonConverter(path); + + assert polygonConverter.getNumTrianglesPerPolygon().size() > 0; + assert polygonConverter.getIndexBuffer().size() > 0; + + logVectorizationInformation(polygonConverter); + } + + private int getByteSize(int[] array) { + return 4 * array.length; + } + + private void logVectorizationInformation(PolygonConverter polygonConverter) throws IOException { + var indexBufferSize = getByteSize(polygonConverter.getIndexBuffer().stream().mapToInt(i -> i).toArray()); + var encodedIndexBuffer = polygonConverter.getEncodedIndexBuffer(); + var percentageOfOriginalSize = (double) encodedIndexBuffer.length / (double) indexBufferSize * 100; + + var gzippedIndexBuffer = polygonConverter.getGzippedIndexBuffer(); + + System.out.println("------------ IndexBuffer result ------------"); + System.out.println("#### Byte size of integer index array: " + indexBufferSize); + System.out.println("#### Byte size of encoded index array: " + encodedIndexBuffer.length); + System.out.println("#### Byte size of gzipped index array: " + gzippedIndexBuffer.length); + System.out.println("------"); + System.out.println("#### Array length of index integer array: " + polygonConverter.getIndexBuffer().size()); + System.out.println("#### Array length of encoded index byte array: " + encodedIndexBuffer.length); + System.out.println("#### Array length of gzipped index byte array: " + gzippedIndexBuffer.length); + System.out.println("---> Encoded vertices are " + percentageOfOriginalSize + " % the size of the original vertex buffer."); + System.out.println(); + + var numTrianglesSize = getByteSize(polygonConverter.getNumTrianglesPerPolygon().stream().mapToInt(i -> i).toArray()); + var encodedNumTriangles = polygonConverter.getEncodedNumberOfTrianglesPerPolygon(); + var percentageOfOriginalNumTrianglesSize = (double) encodedNumTriangles.length / (double) numTrianglesSize * 100; + var gzippedNumTriangles = polygonConverter.getGzippedNumberOfTrianglesPerPolygon(); + + System.out.println("------------ NumTriangles result ------------"); + System.out.println("#### Byte size of integer index array: " + numTrianglesSize); + System.out.println("#### Byte size of encoded index array: " + encodedNumTriangles.length); + System.out.println("#### Byte size of gzipped index array: " + gzippedNumTriangles.length); + System.out.println("------"); + System.out.println("#### Array length of index integer array: " + polygonConverter.getNumTrianglesPerPolygon().size()); + System.out.println("#### Array length of encoded index byte array: " + encodedNumTriangles.length); + System.out.println("#### Array length of gzipped index byte array: " + gzippedNumTriangles.length); + System.out.println("---> Encoded vertices are " + percentageOfOriginalNumTrianglesSize + " % the size of the original vertex buffer."); + System.out.println(); + } + + // TODO: verify log statements + // TODO: verify results with simple vector tile +} From 60d7027eb46c91b6fb62d785328ab8fd9895daef Mon Sep 17 00:00:00 2001 From: Felix Date: Wed, 9 Oct 2024 10:27:32 +0200 Subject: [PATCH 2/8] Refactoring --- .../triangulation/PolygonConverter.java | 123 ------------------ .../triangulation/TriangulatedPolygon.java | 24 ++++ .../triangulation/TriangulationUtils.java | 76 +++++++++++ .../triangulation/VectorTileConverter.java | 98 ++++++++++++++ .../mlt/earcut/EarCutVectorizationTest.java | 46 ++++--- 5 files changed, 228 insertions(+), 139 deletions(-) delete mode 100644 java/src/main/java/com/mlt/converter/triangulation/PolygonConverter.java create mode 100644 java/src/main/java/com/mlt/converter/triangulation/TriangulatedPolygon.java create mode 100644 java/src/main/java/com/mlt/converter/triangulation/TriangulationUtils.java create mode 100644 java/src/main/java/com/mlt/converter/triangulation/VectorTileConverter.java diff --git a/java/src/main/java/com/mlt/converter/triangulation/PolygonConverter.java b/java/src/main/java/com/mlt/converter/triangulation/PolygonConverter.java deleted file mode 100644 index a4976b55..00000000 --- a/java/src/main/java/com/mlt/converter/triangulation/PolygonConverter.java +++ /dev/null @@ -1,123 +0,0 @@ -package com.mlt.converter.triangulation; - -import com.mlt.converter.encodings.EncodingUtils; -import com.mlt.converter.encodings.IntegerEncoder; -import com.mlt.converter.mvt.MapboxVectorTile; -import com.mlt.converter.mvt.MvtUtils; -import com.mlt.data.Feature; -import com.mlt.metadata.stream.LogicalStreamType; -import com.mlt.metadata.stream.OffsetType; -import com.mlt.metadata.stream.PhysicalLevelTechnique; -import com.mlt.metadata.stream.PhysicalStreamType; -import earcut4j.Earcut; -import org.apache.commons.lang3.ArrayUtils; -import org.locationtech.jts.geom.Coordinate; -import org.locationtech.jts.geom.Geometry; -import org.locationtech.jts.geom.MultiPolygon; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; - -public class PolygonConverter { - private final ArrayList numTrianglesPerPolygon = new ArrayList<>(); - - private final ArrayList indexBuffer = new ArrayList<>(); - - public PolygonConverter(Path vectorTilePath) throws IOException { - var vectorTile = MvtUtils.decodeMvt(vectorTilePath); - - this.triangulatePolygons(vectorTile); - } - - public List getNumTrianglesPerPolygon() { - return numTrianglesPerPolygon; - } - - public List getIndexBuffer() { - return indexBuffer; - } - - public byte[] getEncodedIndexBuffer() { - return IntegerEncoder.encodeIntStream(this.indexBuffer, PhysicalLevelTechnique.FAST_PFOR, false, PhysicalStreamType.DATA, new LogicalStreamType(OffsetType.INDEX)); - } - - public byte[] getEncodedNumberOfTrianglesPerPolygon() { - return IntegerEncoder.encodeIntStream(this.numTrianglesPerPolygon, PhysicalLevelTechnique.FAST_PFOR, false, PhysicalStreamType.DATA, new LogicalStreamType(OffsetType.INDEX)); - } - - public byte[] getGzippedIndexBuffer() throws IOException { - return EncodingUtils.gzip(this.getEncodedIndexBuffer()); - } - - public byte[] getGzippedNumberOfTrianglesPerPolygon() throws IOException { - return EncodingUtils.gzip(this.getEncodedNumberOfTrianglesPerPolygon()); - } - - private void triangulatePolygons(MapboxVectorTile vectorTile) { - vectorTile.layers().forEach(layer -> layer.features().forEach(this::triangulatePolygonFeature)); - } - - private void triangulatePolygonFeature(Feature feature) { - if (feature.geometry().getGeometryType().equals(Geometry.TYPENAME_POLYGON)) { - var coordinates = convertCoordinates(feature.geometry().getCoordinates()); - - List triangles = Earcut.earcut(coordinates, null, 2); - - indexBuffer.addAll(triangles); - numTrianglesPerPolygon.add(triangles.size() / 3); - } else if (feature.geometry().getGeometryType().equals(Geometry.TYPENAME_MULTIPOLYGON)) { - triangulatePolygonWithHoles(feature); - } - } - - private void triangulatePolygonWithHoles(Feature feature) { - var multipolygon = (MultiPolygon) feature.geometry(); - var holeIndex = 0; - - ArrayList multiPolygonCoordinates = new ArrayList<>(); - ArrayList holeIndices = new ArrayList<>(); - - for (int i = 0; i < multipolygon.getNumGeometries(); i++) { - // assertion: first polygon defines the outer linear ring and the other polygons its holes! - if (i == 0) { - holeIndex = multipolygon.getGeometryN(i).getCoordinates().length; - holeIndices.add(holeIndex); - } else if (i != multipolygon.getNumGeometries() - 1) { - holeIndex += multipolygon.getGeometryN(i).getCoordinates().length; - holeIndices.add(holeIndex); - } - - var coordinates = multipolygon.getGeometryN(i).getCoordinates(); - for (Coordinate coordinate : coordinates) { - multiPolygonCoordinates.add(coordinate.x); - multiPolygonCoordinates.add(coordinate.y); - if (!Double.isNaN(coordinate.z)) { - multiPolygonCoordinates.add(coordinate.z); - } - } - } - - var doubleArray = ArrayUtils.toPrimitive(multiPolygonCoordinates.toArray(new Double[0])); - List triangleVertices = Earcut.earcut(doubleArray, holeIndices.stream().mapToInt(Integer::intValue).toArray(), 2); - - indexBuffer.addAll(triangleVertices); - numTrianglesPerPolygon.add(triangleVertices.size() / 3); - } - - private double[] convertCoordinates(Coordinate[] coordinates) { - ArrayList convertedCoordinates = new ArrayList<>(); - - for (Coordinate coordinate : coordinates) { - convertedCoordinates.add(coordinate.x); - convertedCoordinates.add(coordinate.y); - if (!Double.isNaN(coordinate.z)) { - convertedCoordinates.add(coordinate.z); - } - } - Double[] array = convertedCoordinates.toArray(new Double[0]); - - return ArrayUtils.toPrimitive(array); - } -} diff --git a/java/src/main/java/com/mlt/converter/triangulation/TriangulatedPolygon.java b/java/src/main/java/com/mlt/converter/triangulation/TriangulatedPolygon.java new file mode 100644 index 00000000..e23b9f37 --- /dev/null +++ b/java/src/main/java/com/mlt/converter/triangulation/TriangulatedPolygon.java @@ -0,0 +1,24 @@ +package com.mlt.converter.triangulation; + +import java.util.ArrayList; +import java.util.List; + +// TODO: more refactoring - remove duplicated code in PolygonConverter +public class TriangulatedPolygon { + private final int numTrianglesPerPolygon; + + private final ArrayList indexBuffer; + + TriangulatedPolygon(ArrayList indexBuffer, int numTriangles) { + this.numTrianglesPerPolygon = numTriangles; + this.indexBuffer = indexBuffer; + } + + public Integer getNumTrianglesPerPolygon() { + return numTrianglesPerPolygon; + } + + public List getIndexBuffer() { + return indexBuffer; + } +} diff --git a/java/src/main/java/com/mlt/converter/triangulation/TriangulationUtils.java b/java/src/main/java/com/mlt/converter/triangulation/TriangulationUtils.java new file mode 100644 index 00000000..a5b179f1 --- /dev/null +++ b/java/src/main/java/com/mlt/converter/triangulation/TriangulationUtils.java @@ -0,0 +1,76 @@ +package com.mlt.converter.triangulation; + +import earcut4j.Earcut; +import org.apache.commons.lang3.ArrayUtils; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.MultiPolygon; +import org.locationtech.jts.geom.Polygon; + +import java.util.ArrayList; +import java.util.List; + +public class TriangulationUtils { + private TriangulationUtils() {} + + static TriangulatedPolygon triangulatePolygon(Polygon polygon) { + var convertedCoordinates = convertCoordinates(polygon.getCoordinates()); + + List triangles = Earcut.earcut(convertedCoordinates, null, 2); + + ArrayList indexBuffer = new ArrayList<>(triangles); + var numTriangles = triangles.size() / 3; + + return new TriangulatedPolygon(indexBuffer, numTriangles); + } + + static TriangulatedPolygon triangulatePolygonWithHoles(MultiPolygon multiPolygon) { + var holeIndex = 0; + + ArrayList multiPolygonCoordinates = new ArrayList<>(); + ArrayList holeIndices = new ArrayList<>(); + + for (int i = 0; i < multiPolygon.getNumGeometries(); i++) { + // assertion: first polygon defines the outer linear ring and the other polygons its holes! + if (i == 0) { + holeIndex = multiPolygon.getGeometryN(i).getCoordinates().length; + holeIndices.add(holeIndex); + } else if (i != multiPolygon.getNumGeometries() - 1) { + holeIndex += multiPolygon.getGeometryN(i).getCoordinates().length; + holeIndices.add(holeIndex); + } + + var coordinates = multiPolygon.getGeometryN(i).getCoordinates(); + for (Coordinate coordinate : coordinates) { + multiPolygonCoordinates.add(coordinate.x); + multiPolygonCoordinates.add(coordinate.y); + if (!Double.isNaN(coordinate.z)) { + multiPolygonCoordinates.add(coordinate.z); + } + } + } + + var doubleArray = ArrayUtils.toPrimitive(multiPolygonCoordinates.toArray(new Double[0])); + List triangleVertices = Earcut.earcut(doubleArray, holeIndices.stream().mapToInt(Integer::intValue).toArray(), 2); + + ArrayList indexBuffer = new ArrayList<>(triangleVertices); + var numTriangles = triangleVertices.size() / 3; + + return new TriangulatedPolygon(indexBuffer, numTriangles); + } + + private static double[] convertCoordinates(Coordinate[] coordinates) { + ArrayList convertedCoordinates = new ArrayList<>(); + + for (Coordinate coordinate : coordinates) { + convertedCoordinates.add(coordinate.x); + convertedCoordinates.add(coordinate.y); + if (!Double.isNaN(coordinate.z)) { + convertedCoordinates.add(coordinate.z); + } + } + Double[] array = convertedCoordinates.toArray(new Double[0]); + + return ArrayUtils.toPrimitive(array); + } +} + diff --git a/java/src/main/java/com/mlt/converter/triangulation/VectorTileConverter.java b/java/src/main/java/com/mlt/converter/triangulation/VectorTileConverter.java new file mode 100644 index 00000000..9fb9eb46 --- /dev/null +++ b/java/src/main/java/com/mlt/converter/triangulation/VectorTileConverter.java @@ -0,0 +1,98 @@ +package com.mlt.converter.triangulation; + +import com.mlt.converter.encodings.EncodingUtils; +import com.mlt.converter.encodings.IntegerEncoder; +import com.mlt.converter.mvt.MapboxVectorTile; +import com.mlt.converter.mvt.MvtUtils; +import com.mlt.data.Feature; +import com.mlt.metadata.stream.LogicalStreamType; +import com.mlt.metadata.stream.OffsetType; +import com.mlt.metadata.stream.PhysicalLevelTechnique; +import com.mlt.metadata.stream.PhysicalStreamType; +import no.ecc.vectortile.VectorTileDecoder; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.MultiPolygon; +import org.locationtech.jts.geom.Polygon; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +public class VectorTileConverter { + private final ArrayList numTrianglesPerPolygon = new ArrayList<>(); + + private final ArrayList indexBuffer = new ArrayList<>(); + + public VectorTileConverter(Path vectorTilePath) throws IOException { + var vectorTile = MvtUtils.decodeMvt(vectorTilePath); + this.triangulatePolygons(vectorTile); + } + + public VectorTileConverter(byte[] mvtTile) throws IOException { + var vectorTile = MvtUtils.decodeMvtFast(mvtTile); + this.triangulatePolygons(vectorTile); + } + + public List getNumTrianglesPerPolygon() { + return numTrianglesPerPolygon; + } + + public List getIndexBuffer() { + return indexBuffer; + } + + public byte[] getEncodedIndexBuffer() { + return IntegerEncoder.encodeIntStream(this.indexBuffer, PhysicalLevelTechnique.FAST_PFOR, false, PhysicalStreamType.DATA, new LogicalStreamType(OffsetType.INDEX)); + } + + public byte[] getEncodedNumberOfTrianglesPerPolygon() { + return IntegerEncoder.encodeIntStream(this.numTrianglesPerPolygon, PhysicalLevelTechnique.FAST_PFOR, false, PhysicalStreamType.DATA, new LogicalStreamType(OffsetType.INDEX)); + } + + public byte[] getGzippedIndexBuffer() throws IOException { + return EncodingUtils.gzip(this.getEncodedIndexBuffer()); + } + + public byte[] getGzippedNumberOfTrianglesPerPolygon() throws IOException { + return EncodingUtils.gzip(this.getEncodedNumberOfTrianglesPerPolygon()); + } + + private void triangulatePolygons(MapboxVectorTile vectorTile) { + vectorTile.layers().forEach(layer -> layer.features().forEach(this::triangulatePolygonFeature)); + } + + private void triangulatePolygons(List decodedTile) { + for (VectorTileDecoder.Feature feature: decodedTile) { + var geometry = feature.getGeometry().toString(); + if (geometry.contains(Geometry.TYPENAME_MULTIPOLYGON.toUpperCase())) { + triangulateMultiPolygon((MultiPolygon) feature.getGeometry()); + } else if (geometry.contains(Geometry.TYPENAME_POLYGON.toUpperCase())) { + triangulatePolygon((Polygon) feature.getGeometry()); + } + } + } + + private void triangulatePolygonFeature(Feature feature) { + if (feature.geometry().getGeometryType().equals(Geometry.TYPENAME_POLYGON)) { + var triangulatedPolygon = TriangulationUtils.triangulatePolygon((Polygon) feature.geometry()); + this.indexBuffer.addAll(triangulatedPolygon.getIndexBuffer()); + this.numTrianglesPerPolygon.add(triangulatedPolygon.getNumTrianglesPerPolygon()); + } else if (feature.geometry().getGeometryType().equals(Geometry.TYPENAME_MULTIPOLYGON)) { + var triangulatedPolygon = TriangulationUtils.triangulatePolygonWithHoles((MultiPolygon) feature.geometry()); + this.indexBuffer.addAll(triangulatedPolygon.getIndexBuffer()); + this.numTrianglesPerPolygon.add(triangulatedPolygon.getNumTrianglesPerPolygon()); } + } + + private void triangulatePolygon(Polygon polygon) { + var triangulatedPolygon = TriangulationUtils.triangulatePolygon(polygon); + this.indexBuffer.addAll(triangulatedPolygon.getIndexBuffer()); + this.numTrianglesPerPolygon.add(triangulatedPolygon.getNumTrianglesPerPolygon()); + } + + private void triangulateMultiPolygon(MultiPolygon multiPolygon) { + var triangulatedPolygon = TriangulationUtils.triangulatePolygonWithHoles(multiPolygon); + this.indexBuffer.addAll(triangulatedPolygon.getIndexBuffer()); + this.numTrianglesPerPolygon.add(triangulatedPolygon.getNumTrianglesPerPolygon()); + } +} diff --git a/java/src/test/java/com/mlt/earcut/EarCutVectorizationTest.java b/java/src/test/java/com/mlt/earcut/EarCutVectorizationTest.java index 4fbc2afe..dd6abc0e 100644 --- a/java/src/test/java/com/mlt/earcut/EarCutVectorizationTest.java +++ b/java/src/test/java/com/mlt/earcut/EarCutVectorizationTest.java @@ -1,9 +1,10 @@ package com.mlt.earcut; -import com.mlt.converter.triangulation.PolygonConverter; +import com.mlt.converter.triangulation.VectorTileConverter; import org.junit.jupiter.api.Test; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Paths; @@ -12,7 +13,7 @@ class EarcutVectorizationTest { void vectorizeVectorTile() throws IOException { var path = Paths.get("../test/fixtures/amazon/5_5_11.pbf").toAbsolutePath(); - var polygonConverter = new PolygonConverter(path); + var polygonConverter = new VectorTileConverter(path); assert polygonConverter.getNumTrianglesPerPolygon().size() > 0; assert polygonConverter.getIndexBuffer().size() > 0; @@ -20,45 +21,58 @@ void vectorizeVectorTile() throws IOException { logVectorizationInformation(polygonConverter); } + @Test + void verifyVectorizedData() throws IOException { + var path = Paths.get("../test/fixtures/omt/13_4266_5468.mvt").toAbsolutePath(); + var encodedTile = Files.readAllBytes(path); + + var polygonConverter = new VectorTileConverter(encodedTile); + + logVectorizationInformation(polygonConverter); + } + private int getByteSize(int[] array) { return 4 * array.length; } - private void logVectorizationInformation(PolygonConverter polygonConverter) throws IOException { - var indexBufferSize = getByteSize(polygonConverter.getIndexBuffer().stream().mapToInt(i -> i).toArray()); - var encodedIndexBuffer = polygonConverter.getEncodedIndexBuffer(); - var percentageOfOriginalSize = (double) encodedIndexBuffer.length / (double) indexBufferSize * 100; + private void logVectorizationInformation(VectorTileConverter vectorTileConverter) throws IOException { + var indexBufferSize = getByteSize(vectorTileConverter.getIndexBuffer().stream().mapToInt(i -> i).toArray()); + var encodedIndexBuffer = vectorTileConverter.getEncodedIndexBuffer(); + var gzippedIndexBuffer = vectorTileConverter.getGzippedIndexBuffer(); + + var encodedPercentageOfOriginalSize = (double) encodedIndexBuffer.length / (double) indexBufferSize * 100; + var gzippedPercentageOfOriginalSize = (double) gzippedIndexBuffer.length / (double) indexBufferSize * 100; - var gzippedIndexBuffer = polygonConverter.getGzippedIndexBuffer(); System.out.println("------------ IndexBuffer result ------------"); System.out.println("#### Byte size of integer index array: " + indexBufferSize); System.out.println("#### Byte size of encoded index array: " + encodedIndexBuffer.length); System.out.println("#### Byte size of gzipped index array: " + gzippedIndexBuffer.length); System.out.println("------"); - System.out.println("#### Array length of index integer array: " + polygonConverter.getIndexBuffer().size()); + System.out.println("#### Array length of index integer array: " + vectorTileConverter.getIndexBuffer().size()); System.out.println("#### Array length of encoded index byte array: " + encodedIndexBuffer.length); System.out.println("#### Array length of gzipped index byte array: " + gzippedIndexBuffer.length); - System.out.println("---> Encoded vertices are " + percentageOfOriginalSize + " % the size of the original vertex buffer."); + System.out.println("---> Encoded vertices are " + encodedPercentageOfOriginalSize + " % the size of the original vertex buffer."); + System.out.println("---> Gzipped vertices are " + gzippedPercentageOfOriginalSize + " % the size of the original vertex buffer."); System.out.println(); - var numTrianglesSize = getByteSize(polygonConverter.getNumTrianglesPerPolygon().stream().mapToInt(i -> i).toArray()); - var encodedNumTriangles = polygonConverter.getEncodedNumberOfTrianglesPerPolygon(); + var numTrianglesSize = getByteSize(vectorTileConverter.getNumTrianglesPerPolygon().stream().mapToInt(i -> i).toArray()); + var encodedNumTriangles = vectorTileConverter.getEncodedNumberOfTrianglesPerPolygon(); + var gzippedNumTriangles = vectorTileConverter.getGzippedNumberOfTrianglesPerPolygon(); var percentageOfOriginalNumTrianglesSize = (double) encodedNumTriangles.length / (double) numTrianglesSize * 100; - var gzippedNumTriangles = polygonConverter.getGzippedNumberOfTrianglesPerPolygon(); + var gzippedPercentageOfOriginalNumTrianglesSize = (double) gzippedNumTriangles.length / (double) numTrianglesSize * 100; System.out.println("------------ NumTriangles result ------------"); System.out.println("#### Byte size of integer index array: " + numTrianglesSize); System.out.println("#### Byte size of encoded index array: " + encodedNumTriangles.length); System.out.println("#### Byte size of gzipped index array: " + gzippedNumTriangles.length); System.out.println("------"); - System.out.println("#### Array length of index integer array: " + polygonConverter.getNumTrianglesPerPolygon().size()); + System.out.println("#### Array length of index integer array: " + vectorTileConverter.getNumTrianglesPerPolygon().size()); System.out.println("#### Array length of encoded index byte array: " + encodedNumTriangles.length); System.out.println("#### Array length of gzipped index byte array: " + gzippedNumTriangles.length); - System.out.println("---> Encoded vertices are " + percentageOfOriginalNumTrianglesSize + " % the size of the original vertex buffer."); + System.out.println("---> Encoded numTriangles are " + percentageOfOriginalNumTrianglesSize + " % the size of the original vertex buffer."); + System.out.println("---> Gzipped numTriangles are " + gzippedPercentageOfOriginalNumTrianglesSize + " % the size of the original vertex buffer."); System.out.println(); } - - // TODO: verify log statements // TODO: verify results with simple vector tile } From 27b830cc00e91fd18d6ae5db5f58e43829f843ee Mon Sep 17 00:00:00 2001 From: Felix Date: Thu, 10 Oct 2024 08:52:46 +0200 Subject: [PATCH 3/8] Add simple tests for polygon triangulation --- java/build.gradle | 1 + .../triangulation/TriangulationUtils.java | 6 +- ...Test.java => EarCutTriangulationTest.java} | 64 +++++++++++++++++-- 3 files changed, 62 insertions(+), 9 deletions(-) rename java/src/test/java/com/mlt/earcut/{EarCutVectorizationTest.java => EarCutTriangulationTest.java} (61%) diff --git a/java/build.gradle b/java/build.gradle index 357c1fd5..5ad885d9 100644 --- a/java/build.gradle +++ b/java/build.gradle @@ -33,6 +33,7 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter-params:5.10.3' testImplementation 'org.openjdk.jmh:jmh-core:1.37 ' testImplementation 'org.openjdk.jmh:jmh-generator-annprocess:1.37' + testImplementation "org.mockito:mockito-core:3.+" } test { diff --git a/java/src/main/java/com/mlt/converter/triangulation/TriangulationUtils.java b/java/src/main/java/com/mlt/converter/triangulation/TriangulationUtils.java index a5b179f1..d8807db1 100644 --- a/java/src/main/java/com/mlt/converter/triangulation/TriangulationUtils.java +++ b/java/src/main/java/com/mlt/converter/triangulation/TriangulationUtils.java @@ -12,7 +12,7 @@ public class TriangulationUtils { private TriangulationUtils() {} - static TriangulatedPolygon triangulatePolygon(Polygon polygon) { + public static TriangulatedPolygon triangulatePolygon(Polygon polygon) { var convertedCoordinates = convertCoordinates(polygon.getCoordinates()); List triangles = Earcut.earcut(convertedCoordinates, null, 2); @@ -23,14 +23,14 @@ static TriangulatedPolygon triangulatePolygon(Polygon polygon) { return new TriangulatedPolygon(indexBuffer, numTriangles); } - static TriangulatedPolygon triangulatePolygonWithHoles(MultiPolygon multiPolygon) { + public static TriangulatedPolygon triangulatePolygonWithHoles(MultiPolygon multiPolygon) { var holeIndex = 0; ArrayList multiPolygonCoordinates = new ArrayList<>(); ArrayList holeIndices = new ArrayList<>(); for (int i = 0; i < multiPolygon.getNumGeometries(); i++) { - // assertion: first polygon defines the outer linear ring and the other polygons its holes! + // assertion: first polygon defines the outer linear ring and the other polygons define its holes! if (i == 0) { holeIndex = multiPolygon.getGeometryN(i).getCoordinates().length; holeIndices.add(holeIndex); diff --git a/java/src/test/java/com/mlt/earcut/EarCutVectorizationTest.java b/java/src/test/java/com/mlt/earcut/EarCutTriangulationTest.java similarity index 61% rename from java/src/test/java/com/mlt/earcut/EarCutVectorizationTest.java rename to java/src/test/java/com/mlt/earcut/EarCutTriangulationTest.java index dd6abc0e..f808c592 100644 --- a/java/src/test/java/com/mlt/earcut/EarCutVectorizationTest.java +++ b/java/src/test/java/com/mlt/earcut/EarCutTriangulationTest.java @@ -1,36 +1,89 @@ package com.mlt.earcut; +import com.mlt.converter.triangulation.TriangulationUtils; import com.mlt.converter.triangulation.VectorTileConverter; import org.junit.jupiter.api.Test; +import org.locationtech.jts.geom.*; +import org.locationtech.jts.util.Assert; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; -class EarcutVectorizationTest { + +class EarcutTriangulationTest { @Test - void vectorizeVectorTile() throws IOException { + void triangulateVectorTile() throws IOException { var path = Paths.get("../test/fixtures/amazon/5_5_11.pbf").toAbsolutePath(); var polygonConverter = new VectorTileConverter(path); - assert polygonConverter.getNumTrianglesPerPolygon().size() > 0; - assert polygonConverter.getIndexBuffer().size() > 0; + Assert.isTrue(polygonConverter.getNumTrianglesPerPolygon().size() > 0); + Assert.isTrue(polygonConverter.getIndexBuffer().size() > 0); logVectorizationInformation(polygonConverter); } @Test - void verifyVectorizedData() throws IOException { + void triangulateVectorTile2() throws IOException { var path = Paths.get("../test/fixtures/omt/13_4266_5468.mvt").toAbsolutePath(); var encodedTile = Files.readAllBytes(path); var polygonConverter = new VectorTileConverter(encodedTile); + Assert.isTrue(polygonConverter.getNumTrianglesPerPolygon().size() > 0); + Assert.isTrue(polygonConverter.getIndexBuffer().size() > 0); logVectorizationInformation(polygonConverter); } + @Test + void verifyPolygonTriangulation() { + Polygon polygon = mock(Polygon.class); + + when(polygon.getCoordinates()).thenReturn(getQuadraticPolygonCoordinates()); + var triangulatedPolygon = TriangulationUtils.triangulatePolygon(polygon); + + Assert.isTrue(triangulatedPolygon.getNumTrianglesPerPolygon() == 2); + Assert.isTrue(triangulatedPolygon.getIndexBuffer().size() == 6); + } + + @Test + void verifyMultiPolygonTriangulation() { + MultiPolygon multiPolygon = mock(MultiPolygon.class); + Polygon outerPolygon = mock(Polygon.class); + when(outerPolygon.getCoordinates()).thenReturn(getQuadraticPolygonCoordinates()); + + Polygon innerPolygon = mock(Polygon.class); + Coordinate[] innerPolygonCoordinates = new Coordinate[4]; + innerPolygonCoordinates[0] = new Coordinate(0.25, 0.25); + innerPolygonCoordinates[1] = new Coordinate(0.75, 0.25); + innerPolygonCoordinates[2] = new Coordinate(0.75, 0.75); + innerPolygonCoordinates[3] = new Coordinate(0.25, 0.75); + + when(innerPolygon.getCoordinates()).thenReturn(innerPolygonCoordinates); + + when(multiPolygon.getNumGeometries()).thenReturn(2); + when(multiPolygon.getGeometryN(0)).thenReturn(outerPolygon); + when(multiPolygon.getGeometryN(1)).thenReturn(innerPolygon); + + var triangulatedPolygon = TriangulationUtils.triangulatePolygonWithHoles(multiPolygon); + + Assert.isTrue(triangulatedPolygon.getNumTrianglesPerPolygon() == 8); + Assert.isTrue(triangulatedPolygon.getIndexBuffer().size() == 24); + } + + private Coordinate[] getQuadraticPolygonCoordinates() { + Coordinate[] coordinates = new Coordinate[4]; + coordinates[0] = new Coordinate(0,0); + coordinates[1]= new Coordinate(1,0); + coordinates[2] = new Coordinate(1,1); + coordinates[3] = new Coordinate(0,1); + return coordinates; + } + private int getByteSize(int[] array) { return 4 * array.length; } @@ -74,5 +127,4 @@ private void logVectorizationInformation(VectorTileConverter vectorTileConverter System.out.println("---> Gzipped numTriangles are " + gzippedPercentageOfOriginalNumTrianglesSize + " % the size of the original vertex buffer."); System.out.println(); } - // TODO: verify results with simple vector tile } From 6cc85febad9fe6fe20218cccbcdb86dfdcdfc189 Mon Sep 17 00:00:00 2001 From: Felix Date: Wed, 6 Nov 2024 14:14:39 +0100 Subject: [PATCH 4/8] Integrate option to triangulate polygons to GeometryEncoder --- .../java/com/mlt/converter/MltConverter.java | 4 +- .../converter/encodings/GeometryEncoder.java | 93 ++++-- .../triangulation/TriangulatedPolygon.java | 25 +- .../triangulation/TriangulationUtils.java | 118 ++++---- .../triangulation/VectorTileConverter.java | 175 +++++++----- .../mlt/earcut/EarCutTriangulationTest.java | 268 ++++++++++-------- 6 files changed, 392 insertions(+), 291 deletions(-) diff --git a/java/src/main/java/com/mlt/converter/MltConverter.java b/java/src/main/java/com/mlt/converter/MltConverter.java index 50dfe37c..5d3dad22 100644 --- a/java/src/main/java/com/mlt/converter/MltConverter.java +++ b/java/src/main/java/com/mlt/converter/MltConverter.java @@ -199,7 +199,7 @@ public static byte[] convertMvt( /* Encode geometry and property columns */ var encodedGeometryColumn = GeometryEncoder.encodeGeometryColumn( - geometries, physicalLevelTechnique, new ArrayList<>()); + geometries, physicalLevelTechnique, new ArrayList<>(), false); var encodedGeometryFieldMetadata = EncodingUtils.encodeVarints(new long[] {encodedGeometryColumn.getLeft()}, false, false); var propertyColumns = filterPropertyColumns(featureTableMetadata); @@ -294,7 +294,7 @@ private static List sortAndOptimizeFeatures( /** Sort geometries and reassign new Ids in ascending order to the features */ var ids = features.stream().map(f -> f.id()).sorted().collect(Collectors.toList()); // TODO: refactor -> quick and dirty way to sort features based on geometry - GeometryEncoder.encodeGeometryColumn(geometries, physicalLevelTechnique, ids); + GeometryEncoder.encodeGeometryColumn(geometries, physicalLevelTechnique, ids, false); var sortedFeatures = new ArrayList(); var idCounter = 0; for (var newId : ids) { diff --git a/java/src/main/java/com/mlt/converter/encodings/GeometryEncoder.java b/java/src/main/java/com/mlt/converter/encodings/GeometryEncoder.java index 2a485ca6..a2994955 100644 --- a/java/src/main/java/com/mlt/converter/encodings/GeometryEncoder.java +++ b/java/src/main/java/com/mlt/converter/encodings/GeometryEncoder.java @@ -6,6 +6,7 @@ import com.google.common.collect.Lists; import com.mlt.converter.CollectionUtils; import com.mlt.converter.geometry.*; +import com.mlt.converter.triangulation.VectorTileConverter; import com.mlt.metadata.stream.*; import java.util.*; import java.util.function.Function; @@ -29,12 +30,15 @@ private GeometryEncoder() {} public static Triple encodeGeometryColumn( List geometries, PhysicalLevelTechnique physicalLevelTechnique, - List featureIds) { + List featureIds, + boolean triangulatePolygons) { var geometryTypes = new ArrayList(); var numGeometries = new ArrayList(); var numParts = new ArrayList(); var numRings = new ArrayList(); var vertexBuffer = new ArrayList(); + var numTrianglesPerPolygon = new ArrayList(); + var indexBuffer = new ArrayList(); var containsPolygon = geometries.stream() .anyMatch( @@ -70,6 +74,11 @@ public static Triple encodeGeometryColumn( var polygon = (Polygon) geometry; var vertices = flatPolygon(polygon, numParts, numRings); vertexBuffer.addAll(vertices); + if (triangulatePolygons) { + var triangulatedPolygon = new VectorTileConverter(polygon); + indexBuffer.addAll(triangulatedPolygon.getIndexBuffer()); + numTrianglesPerPolygon.addAll(triangulatedPolygon.getNumTrianglesPerPolygon()); + } break; } case Geometry.TYPENAME_MULTILINESTRING: @@ -98,6 +107,11 @@ public static Triple encodeGeometryColumn( var vertices = flatPolygon(polygon, numParts, numRings); vertexBuffer.addAll(vertices); } + if (triangulatePolygons) { + var triangulatedPolygon = new VectorTileConverter(multiPolygon); + indexBuffer.addAll(triangulatedPolygon.getIndexBuffer()); + numTrianglesPerPolygon.addAll(triangulatedPolygon.getNumTrianglesPerPolygon()); + } break; } default: @@ -145,6 +159,21 @@ public static Triple encodeGeometryColumn( Arrays.stream(zigZagDeltaVertexBuffer).boxed().collect(Collectors.toList()), physicalLevelTechnique, false); + + var encodedIndexBuffer = + IntegerEncoder.encodeIntStream( + indexBuffer, + physicalLevelTechnique, + false, + PhysicalStreamType.OFFSET, + new LogicalStreamType(OffsetType.INDEX)); + var encodedNumTrianglesBuffer = + IntegerEncoder.encodeIntStream( + numTrianglesPerPolygon, + physicalLevelTechnique, + false, + PhysicalStreamType.OFFSET, + new LogicalStreamType(OffsetType.INDEX)); // TODO: should we do a potential recursive encoding again var encodedVertexDictionary = IntegerEncoder.encodeInt( @@ -223,10 +252,15 @@ public static Triple encodeGeometryColumn( Arrays.stream(zigZagDeltaVertexBuffer).boxed().collect(Collectors.toList()), physicalLevelTechnique); numStreams++; - return Triple.of( - numStreams, - ArrayUtils.addAll(encodedTopologyStreams, encodedVertexBufferStream), - maxVertexValue); + var result = + Triple.of( + numStreams, + ArrayUtils.addAll(encodedTopologyStreams, encodedVertexBufferStream), + maxVertexValue); + + return triangulatePolygons + ? buildTriangulatedTriple(result, encodedIndexBuffer, encodedNumTrianglesBuffer) + : result; } else if (dictionaryEncodedSize < plainVertexBufferSize && dictionaryEncodedSize <= mortonDictionaryEncodedSize) { var encodedVertexOffsetStream = @@ -241,11 +275,17 @@ public static Triple encodeGeometryColumn( Arrays.stream(zigZagDeltaVertexDictionary).boxed().collect(Collectors.toList()), physicalLevelTechnique); numStreams += 2; - return Triple.of( - numStreams, - CollectionUtils.concatByteArrays( - encodedTopologyStreams, encodedVertexOffsetStream, encodedVertexDictionaryStream), - maxVertexValue); + + var result = + Triple.of( + numStreams, + CollectionUtils.concatByteArrays( + encodedTopologyStreams, encodedVertexOffsetStream, encodedVertexDictionaryStream), + maxVertexValue); + + return triangulatePolygons + ? buildTriangulatedTriple(result, encodedIndexBuffer, encodedNumTrianglesBuffer) + : result; } else { var encodedMortonVertexOffsetStream = IntegerEncoder.encodeIntStream( @@ -266,13 +306,18 @@ public static Triple encodeGeometryColumn( ((encodedDictionaryOffsets.encodedValues.length + encodedVertexDictionary.encodedValues.length) - (encodedMortonEncodedDictionaryOffsets.encodedValues.length + encodedMortonVertexDictionary.encodedValues.length)) /1000d); System.out.println("Morton VertexDictionary encoding size: " + encodedMortonVertexOffsetStream.length /1000d);*/ - return Triple.of( - numStreams, - CollectionUtils.concatByteArrays( - encodedTopologyStreams, - encodedMortonVertexOffsetStream, - encodedMortonEncodedVertexDictionaryStream), - maxVertexValue); + var result = + Triple.of( + numStreams, + CollectionUtils.concatByteArrays( + encodedTopologyStreams, + encodedMortonVertexOffsetStream, + encodedMortonEncodedVertexDictionaryStream), + maxVertexValue); + + return triangulatePolygons + ? buildTriangulatedTriple(result, encodedIndexBuffer, encodedNumTrianglesBuffer) + : result; } } @@ -396,7 +441,6 @@ private static List flatLineString(LineString lineString) { private static List flatPolygon( Polygon polygon, List partSize, List ringSize) { - var vertexBuffer = new ArrayList(); var numRings = polygon.getNumInteriorRing() + 1; partSize.add(numRings); @@ -407,7 +451,7 @@ private static List flatPolygon( Arrays.copyOf( exteriorRing.getCoordinates(), exteriorRing.getCoordinates().length - 1)); var shellVertices = flatLineString(shell); - vertexBuffer.addAll(shellVertices); + var vertexBuffer = new ArrayList(shellVertices); ringSize.add(shell.getNumPoints()); for (var i = 0; i < polygon.getNumInteriorRing(); i++) { @@ -450,4 +494,15 @@ private static byte[] encodeVertexBuffer( return ArrayUtils.addAll(encodedMetadata, encodedValues); } + + private static Triple buildTriangulatedTriple( + Triple triple, + byte[] encodedIndexBuffer, + byte[] encodedNumTrianglesPerPolygon) { + return Triple.of( + triple.getLeft() + 2, + CollectionUtils.concatByteArrays( + triple.getMiddle(), encodedIndexBuffer, encodedNumTrianglesPerPolygon), + triple.getRight()); + } } diff --git a/java/src/main/java/com/mlt/converter/triangulation/TriangulatedPolygon.java b/java/src/main/java/com/mlt/converter/triangulation/TriangulatedPolygon.java index e23b9f37..af63a7d5 100644 --- a/java/src/main/java/com/mlt/converter/triangulation/TriangulatedPolygon.java +++ b/java/src/main/java/com/mlt/converter/triangulation/TriangulatedPolygon.java @@ -3,22 +3,21 @@ import java.util.ArrayList; import java.util.List; -// TODO: more refactoring - remove duplicated code in PolygonConverter public class TriangulatedPolygon { - private final int numTrianglesPerPolygon; + private final int numTrianglesPerPolygon; - private final ArrayList indexBuffer; + private final ArrayList indexBuffer; - TriangulatedPolygon(ArrayList indexBuffer, int numTriangles) { - this.numTrianglesPerPolygon = numTriangles; - this.indexBuffer = indexBuffer; - } + TriangulatedPolygon(ArrayList indexBuffer, int numTriangles) { + this.numTrianglesPerPolygon = numTriangles; + this.indexBuffer = indexBuffer; + } - public Integer getNumTrianglesPerPolygon() { - return numTrianglesPerPolygon; - } + public Integer getNumTrianglesPerPolygon() { + return numTrianglesPerPolygon; + } - public List getIndexBuffer() { - return indexBuffer; - } + public List getIndexBuffer() { + return indexBuffer; + } } diff --git a/java/src/main/java/com/mlt/converter/triangulation/TriangulationUtils.java b/java/src/main/java/com/mlt/converter/triangulation/TriangulationUtils.java index d8807db1..47cb7054 100644 --- a/java/src/main/java/com/mlt/converter/triangulation/TriangulationUtils.java +++ b/java/src/main/java/com/mlt/converter/triangulation/TriangulationUtils.java @@ -1,76 +1,76 @@ package com.mlt.converter.triangulation; import earcut4j.Earcut; +import java.util.ArrayList; +import java.util.List; import org.apache.commons.lang3.ArrayUtils; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.MultiPolygon; import org.locationtech.jts.geom.Polygon; -import java.util.ArrayList; -import java.util.List; - public class TriangulationUtils { - private TriangulationUtils() {} - - public static TriangulatedPolygon triangulatePolygon(Polygon polygon) { - var convertedCoordinates = convertCoordinates(polygon.getCoordinates()); - - List triangles = Earcut.earcut(convertedCoordinates, null, 2); - - ArrayList indexBuffer = new ArrayList<>(triangles); - var numTriangles = triangles.size() / 3; - - return new TriangulatedPolygon(indexBuffer, numTriangles); - } - - public static TriangulatedPolygon triangulatePolygonWithHoles(MultiPolygon multiPolygon) { - var holeIndex = 0; - - ArrayList multiPolygonCoordinates = new ArrayList<>(); - ArrayList holeIndices = new ArrayList<>(); - - for (int i = 0; i < multiPolygon.getNumGeometries(); i++) { - // assertion: first polygon defines the outer linear ring and the other polygons define its holes! - if (i == 0) { - holeIndex = multiPolygon.getGeometryN(i).getCoordinates().length; - holeIndices.add(holeIndex); - } else if (i != multiPolygon.getNumGeometries() - 1) { - holeIndex += multiPolygon.getGeometryN(i).getCoordinates().length; - holeIndices.add(holeIndex); - } - - var coordinates = multiPolygon.getGeometryN(i).getCoordinates(); - for (Coordinate coordinate : coordinates) { - multiPolygonCoordinates.add(coordinate.x); - multiPolygonCoordinates.add(coordinate.y); - if (!Double.isNaN(coordinate.z)) { - multiPolygonCoordinates.add(coordinate.z); - } - } + private TriangulationUtils() {} + + public static TriangulatedPolygon triangulatePolygon(Polygon polygon) { + var convertedCoordinates = convertCoordinates(polygon.getCoordinates()); + + List triangles = Earcut.earcut(convertedCoordinates, null, 2); + + ArrayList indexBuffer = new ArrayList<>(triangles); + var numTriangles = triangles.size() / 3; + + return new TriangulatedPolygon(indexBuffer, numTriangles); + } + + public static TriangulatedPolygon triangulatePolygonWithHoles(MultiPolygon multiPolygon) { + var holeIndex = 0; + + ArrayList multiPolygonCoordinates = new ArrayList<>(); + ArrayList holeIndices = new ArrayList<>(); + + for (int i = 0; i < multiPolygon.getNumGeometries(); i++) { + // assertion: first polygon defines the outer linear ring and the other polygons define its + // holes! + if (i == 0) { + holeIndex = multiPolygon.getGeometryN(i).getCoordinates().length; + holeIndices.add(holeIndex); + } else if (i != multiPolygon.getNumGeometries() - 1) { + holeIndex += multiPolygon.getGeometryN(i).getCoordinates().length; + holeIndices.add(holeIndex); + } + + var coordinates = multiPolygon.getGeometryN(i).getCoordinates(); + for (Coordinate coordinate : coordinates) { + multiPolygonCoordinates.add(coordinate.x); + multiPolygonCoordinates.add(coordinate.y); + if (!Double.isNaN(coordinate.z)) { + multiPolygonCoordinates.add(coordinate.z); } + } + } - var doubleArray = ArrayUtils.toPrimitive(multiPolygonCoordinates.toArray(new Double[0])); - List triangleVertices = Earcut.earcut(doubleArray, holeIndices.stream().mapToInt(Integer::intValue).toArray(), 2); - - ArrayList indexBuffer = new ArrayList<>(triangleVertices); - var numTriangles = triangleVertices.size() / 3; + var doubleArray = ArrayUtils.toPrimitive(multiPolygonCoordinates.toArray(new Double[0])); + List triangleVertices = + Earcut.earcut(doubleArray, holeIndices.stream().mapToInt(Integer::intValue).toArray(), 2); - return new TriangulatedPolygon(indexBuffer, numTriangles); - } + ArrayList indexBuffer = new ArrayList<>(triangleVertices); + var numTriangles = triangleVertices.size() / 3; - private static double[] convertCoordinates(Coordinate[] coordinates) { - ArrayList convertedCoordinates = new ArrayList<>(); + return new TriangulatedPolygon(indexBuffer, numTriangles); + } - for (Coordinate coordinate : coordinates) { - convertedCoordinates.add(coordinate.x); - convertedCoordinates.add(coordinate.y); - if (!Double.isNaN(coordinate.z)) { - convertedCoordinates.add(coordinate.z); - } - } - Double[] array = convertedCoordinates.toArray(new Double[0]); + private static double[] convertCoordinates(Coordinate[] coordinates) { + ArrayList convertedCoordinates = new ArrayList<>(); - return ArrayUtils.toPrimitive(array); + for (Coordinate coordinate : coordinates) { + convertedCoordinates.add(coordinate.x); + convertedCoordinates.add(coordinate.y); + if (!Double.isNaN(coordinate.z)) { + convertedCoordinates.add(coordinate.z); + } } -} + Double[] array = convertedCoordinates.toArray(new Double[0]); + return ArrayUtils.toPrimitive(array); + } +} diff --git a/java/src/main/java/com/mlt/converter/triangulation/VectorTileConverter.java b/java/src/main/java/com/mlt/converter/triangulation/VectorTileConverter.java index 9fb9eb46..f0bed879 100644 --- a/java/src/main/java/com/mlt/converter/triangulation/VectorTileConverter.java +++ b/java/src/main/java/com/mlt/converter/triangulation/VectorTileConverter.java @@ -9,90 +9,109 @@ import com.mlt.metadata.stream.OffsetType; import com.mlt.metadata.stream.PhysicalLevelTechnique; import com.mlt.metadata.stream.PhysicalStreamType; -import no.ecc.vectortile.VectorTileDecoder; -import org.locationtech.jts.geom.Geometry; -import org.locationtech.jts.geom.MultiPolygon; -import org.locationtech.jts.geom.Polygon; - import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import no.ecc.vectortile.VectorTileDecoder; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.MultiPolygon; +import org.locationtech.jts.geom.Polygon; public class VectorTileConverter { - private final ArrayList numTrianglesPerPolygon = new ArrayList<>(); - - private final ArrayList indexBuffer = new ArrayList<>(); - - public VectorTileConverter(Path vectorTilePath) throws IOException { - var vectorTile = MvtUtils.decodeMvt(vectorTilePath); - this.triangulatePolygons(vectorTile); - } - - public VectorTileConverter(byte[] mvtTile) throws IOException { - var vectorTile = MvtUtils.decodeMvtFast(mvtTile); - this.triangulatePolygons(vectorTile); - } - - public List getNumTrianglesPerPolygon() { - return numTrianglesPerPolygon; - } - - public List getIndexBuffer() { - return indexBuffer; + private final ArrayList numTrianglesPerPolygon = new ArrayList<>(); + + private final ArrayList indexBuffer = new ArrayList<>(); + + public VectorTileConverter(Path vectorTilePath) throws IOException { + var vectorTile = MvtUtils.decodeMvt(vectorTilePath); + this.triangulatePolygons(vectorTile); + } + + public VectorTileConverter(byte[] mvtTile) throws IOException { + var vectorTile = MvtUtils.decodeMvtFast(mvtTile); + this.triangulatePolygons(vectorTile); + } + + public VectorTileConverter(Polygon polygon) { + this.triangulatePolygon(polygon); + } + + public VectorTileConverter(MultiPolygon multiPolygon) { + this.triangulateMultiPolygon(multiPolygon); + } + + public List getNumTrianglesPerPolygon() { + return numTrianglesPerPolygon; + } + + public List getIndexBuffer() { + return indexBuffer; + } + + public byte[] getEncodedIndexBuffer() { + return IntegerEncoder.encodeIntStream( + this.indexBuffer, + PhysicalLevelTechnique.FAST_PFOR, + false, + PhysicalStreamType.DATA, + new LogicalStreamType(OffsetType.INDEX)); + } + + public byte[] getEncodedNumberOfTrianglesPerPolygon() { + return IntegerEncoder.encodeIntStream( + this.numTrianglesPerPolygon, + PhysicalLevelTechnique.FAST_PFOR, + false, + PhysicalStreamType.DATA, + new LogicalStreamType(OffsetType.INDEX)); + } + + public byte[] getGzippedIndexBuffer() throws IOException { + return EncodingUtils.gzip(this.getEncodedIndexBuffer()); + } + + public byte[] getGzippedNumberOfTrianglesPerPolygon() throws IOException { + return EncodingUtils.gzip(this.getEncodedNumberOfTrianglesPerPolygon()); + } + + private void triangulatePolygons(MapboxVectorTile vectorTile) { + vectorTile.layers().forEach(layer -> layer.features().forEach(this::triangulatePolygonFeature)); + } + + private void triangulatePolygons(List decodedTile) { + for (VectorTileDecoder.Feature feature : decodedTile) { + var geometry = feature.getGeometry().toString(); + if (geometry.contains(Geometry.TYPENAME_MULTIPOLYGON.toUpperCase())) { + triangulateMultiPolygon((MultiPolygon) feature.getGeometry()); + } else if (geometry.contains(Geometry.TYPENAME_POLYGON.toUpperCase())) { + triangulatePolygon((Polygon) feature.getGeometry()); + } } - - public byte[] getEncodedIndexBuffer() { - return IntegerEncoder.encodeIntStream(this.indexBuffer, PhysicalLevelTechnique.FAST_PFOR, false, PhysicalStreamType.DATA, new LogicalStreamType(OffsetType.INDEX)); - } - - public byte[] getEncodedNumberOfTrianglesPerPolygon() { - return IntegerEncoder.encodeIntStream(this.numTrianglesPerPolygon, PhysicalLevelTechnique.FAST_PFOR, false, PhysicalStreamType.DATA, new LogicalStreamType(OffsetType.INDEX)); - } - - public byte[] getGzippedIndexBuffer() throws IOException { - return EncodingUtils.gzip(this.getEncodedIndexBuffer()); - } - - public byte[] getGzippedNumberOfTrianglesPerPolygon() throws IOException { - return EncodingUtils.gzip(this.getEncodedNumberOfTrianglesPerPolygon()); - } - - private void triangulatePolygons(MapboxVectorTile vectorTile) { - vectorTile.layers().forEach(layer -> layer.features().forEach(this::triangulatePolygonFeature)); - } - - private void triangulatePolygons(List decodedTile) { - for (VectorTileDecoder.Feature feature: decodedTile) { - var geometry = feature.getGeometry().toString(); - if (geometry.contains(Geometry.TYPENAME_MULTIPOLYGON.toUpperCase())) { - triangulateMultiPolygon((MultiPolygon) feature.getGeometry()); - } else if (geometry.contains(Geometry.TYPENAME_POLYGON.toUpperCase())) { - triangulatePolygon((Polygon) feature.getGeometry()); - } - } - } - - private void triangulatePolygonFeature(Feature feature) { - if (feature.geometry().getGeometryType().equals(Geometry.TYPENAME_POLYGON)) { - var triangulatedPolygon = TriangulationUtils.triangulatePolygon((Polygon) feature.geometry()); - this.indexBuffer.addAll(triangulatedPolygon.getIndexBuffer()); - this.numTrianglesPerPolygon.add(triangulatedPolygon.getNumTrianglesPerPolygon()); - } else if (feature.geometry().getGeometryType().equals(Geometry.TYPENAME_MULTIPOLYGON)) { - var triangulatedPolygon = TriangulationUtils.triangulatePolygonWithHoles((MultiPolygon) feature.geometry()); - this.indexBuffer.addAll(triangulatedPolygon.getIndexBuffer()); - this.numTrianglesPerPolygon.add(triangulatedPolygon.getNumTrianglesPerPolygon()); } - } - - private void triangulatePolygon(Polygon polygon) { - var triangulatedPolygon = TriangulationUtils.triangulatePolygon(polygon); - this.indexBuffer.addAll(triangulatedPolygon.getIndexBuffer()); - this.numTrianglesPerPolygon.add(triangulatedPolygon.getNumTrianglesPerPolygon()); - } - - private void triangulateMultiPolygon(MultiPolygon multiPolygon) { - var triangulatedPolygon = TriangulationUtils.triangulatePolygonWithHoles(multiPolygon); - this.indexBuffer.addAll(triangulatedPolygon.getIndexBuffer()); - this.numTrianglesPerPolygon.add(triangulatedPolygon.getNumTrianglesPerPolygon()); + } + + private void triangulatePolygonFeature(Feature feature) { + if (feature.geometry().getGeometryType().equals(Geometry.TYPENAME_POLYGON)) { + var triangulatedPolygon = TriangulationUtils.triangulatePolygon((Polygon) feature.geometry()); + this.indexBuffer.addAll(triangulatedPolygon.getIndexBuffer()); + this.numTrianglesPerPolygon.add(triangulatedPolygon.getNumTrianglesPerPolygon()); + } else if (feature.geometry().getGeometryType().equals(Geometry.TYPENAME_MULTIPOLYGON)) { + var triangulatedPolygon = + TriangulationUtils.triangulatePolygonWithHoles((MultiPolygon) feature.geometry()); + this.indexBuffer.addAll(triangulatedPolygon.getIndexBuffer()); + this.numTrianglesPerPolygon.add(triangulatedPolygon.getNumTrianglesPerPolygon()); } + } + + private void triangulatePolygon(Polygon polygon) { + var triangulatedPolygon = TriangulationUtils.triangulatePolygon(polygon); + this.indexBuffer.addAll(triangulatedPolygon.getIndexBuffer()); + this.numTrianglesPerPolygon.add(triangulatedPolygon.getNumTrianglesPerPolygon()); + } + + private void triangulateMultiPolygon(MultiPolygon multiPolygon) { + var triangulatedPolygon = TriangulationUtils.triangulatePolygonWithHoles(multiPolygon); + this.indexBuffer.addAll(triangulatedPolygon.getIndexBuffer()); + this.numTrianglesPerPolygon.add(triangulatedPolygon.getNumTrianglesPerPolygon()); + } } diff --git a/java/src/test/java/com/mlt/earcut/EarCutTriangulationTest.java b/java/src/test/java/com/mlt/earcut/EarCutTriangulationTest.java index f808c592..3166cfc1 100644 --- a/java/src/test/java/com/mlt/earcut/EarCutTriangulationTest.java +++ b/java/src/test/java/com/mlt/earcut/EarCutTriangulationTest.java @@ -1,130 +1,158 @@ package com.mlt.earcut; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import com.mlt.converter.triangulation.TriangulationUtils; import com.mlt.converter.triangulation.VectorTileConverter; -import org.junit.jupiter.api.Test; -import org.locationtech.jts.geom.*; -import org.locationtech.jts.util.Assert; - import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - +import org.junit.jupiter.api.Test; +import org.locationtech.jts.geom.*; +import org.locationtech.jts.util.Assert; class EarcutTriangulationTest { - @Test - void triangulateVectorTile() throws IOException { - var path = Paths.get("../test/fixtures/amazon/5_5_11.pbf").toAbsolutePath(); - - var polygonConverter = new VectorTileConverter(path); - - Assert.isTrue(polygonConverter.getNumTrianglesPerPolygon().size() > 0); - Assert.isTrue(polygonConverter.getIndexBuffer().size() > 0); - - logVectorizationInformation(polygonConverter); - } - - @Test - void triangulateVectorTile2() throws IOException { - var path = Paths.get("../test/fixtures/omt/13_4266_5468.mvt").toAbsolutePath(); - var encodedTile = Files.readAllBytes(path); - - var polygonConverter = new VectorTileConverter(encodedTile); - - Assert.isTrue(polygonConverter.getNumTrianglesPerPolygon().size() > 0); - Assert.isTrue(polygonConverter.getIndexBuffer().size() > 0); - logVectorizationInformation(polygonConverter); - } - - @Test - void verifyPolygonTriangulation() { - Polygon polygon = mock(Polygon.class); - - when(polygon.getCoordinates()).thenReturn(getQuadraticPolygonCoordinates()); - var triangulatedPolygon = TriangulationUtils.triangulatePolygon(polygon); - - Assert.isTrue(triangulatedPolygon.getNumTrianglesPerPolygon() == 2); - Assert.isTrue(triangulatedPolygon.getIndexBuffer().size() == 6); - } - - @Test - void verifyMultiPolygonTriangulation() { - MultiPolygon multiPolygon = mock(MultiPolygon.class); - Polygon outerPolygon = mock(Polygon.class); - when(outerPolygon.getCoordinates()).thenReturn(getQuadraticPolygonCoordinates()); - - Polygon innerPolygon = mock(Polygon.class); - Coordinate[] innerPolygonCoordinates = new Coordinate[4]; - innerPolygonCoordinates[0] = new Coordinate(0.25, 0.25); - innerPolygonCoordinates[1] = new Coordinate(0.75, 0.25); - innerPolygonCoordinates[2] = new Coordinate(0.75, 0.75); - innerPolygonCoordinates[3] = new Coordinate(0.25, 0.75); - - when(innerPolygon.getCoordinates()).thenReturn(innerPolygonCoordinates); - - when(multiPolygon.getNumGeometries()).thenReturn(2); - when(multiPolygon.getGeometryN(0)).thenReturn(outerPolygon); - when(multiPolygon.getGeometryN(1)).thenReturn(innerPolygon); - - var triangulatedPolygon = TriangulationUtils.triangulatePolygonWithHoles(multiPolygon); - - Assert.isTrue(triangulatedPolygon.getNumTrianglesPerPolygon() == 8); - Assert.isTrue(triangulatedPolygon.getIndexBuffer().size() == 24); - } - - private Coordinate[] getQuadraticPolygonCoordinates() { - Coordinate[] coordinates = new Coordinate[4]; - coordinates[0] = new Coordinate(0,0); - coordinates[1]= new Coordinate(1,0); - coordinates[2] = new Coordinate(1,1); - coordinates[3] = new Coordinate(0,1); - return coordinates; - } - - private int getByteSize(int[] array) { - return 4 * array.length; - } - - private void logVectorizationInformation(VectorTileConverter vectorTileConverter) throws IOException { - var indexBufferSize = getByteSize(vectorTileConverter.getIndexBuffer().stream().mapToInt(i -> i).toArray()); - var encodedIndexBuffer = vectorTileConverter.getEncodedIndexBuffer(); - var gzippedIndexBuffer = vectorTileConverter.getGzippedIndexBuffer(); - - var encodedPercentageOfOriginalSize = (double) encodedIndexBuffer.length / (double) indexBufferSize * 100; - var gzippedPercentageOfOriginalSize = (double) gzippedIndexBuffer.length / (double) indexBufferSize * 100; - - - System.out.println("------------ IndexBuffer result ------------"); - System.out.println("#### Byte size of integer index array: " + indexBufferSize); - System.out.println("#### Byte size of encoded index array: " + encodedIndexBuffer.length); - System.out.println("#### Byte size of gzipped index array: " + gzippedIndexBuffer.length); - System.out.println("------"); - System.out.println("#### Array length of index integer array: " + vectorTileConverter.getIndexBuffer().size()); - System.out.println("#### Array length of encoded index byte array: " + encodedIndexBuffer.length); - System.out.println("#### Array length of gzipped index byte array: " + gzippedIndexBuffer.length); - System.out.println("---> Encoded vertices are " + encodedPercentageOfOriginalSize + " % the size of the original vertex buffer."); - System.out.println("---> Gzipped vertices are " + gzippedPercentageOfOriginalSize + " % the size of the original vertex buffer."); - System.out.println(); - - var numTrianglesSize = getByteSize(vectorTileConverter.getNumTrianglesPerPolygon().stream().mapToInt(i -> i).toArray()); - var encodedNumTriangles = vectorTileConverter.getEncodedNumberOfTrianglesPerPolygon(); - var gzippedNumTriangles = vectorTileConverter.getGzippedNumberOfTrianglesPerPolygon(); - var percentageOfOriginalNumTrianglesSize = (double) encodedNumTriangles.length / (double) numTrianglesSize * 100; - var gzippedPercentageOfOriginalNumTrianglesSize = (double) gzippedNumTriangles.length / (double) numTrianglesSize * 100; - - System.out.println("------------ NumTriangles result ------------"); - System.out.println("#### Byte size of integer index array: " + numTrianglesSize); - System.out.println("#### Byte size of encoded index array: " + encodedNumTriangles.length); - System.out.println("#### Byte size of gzipped index array: " + gzippedNumTriangles.length); - System.out.println("------"); - System.out.println("#### Array length of index integer array: " + vectorTileConverter.getNumTrianglesPerPolygon().size()); - System.out.println("#### Array length of encoded index byte array: " + encodedNumTriangles.length); - System.out.println("#### Array length of gzipped index byte array: " + gzippedNumTriangles.length); - System.out.println("---> Encoded numTriangles are " + percentageOfOriginalNumTrianglesSize + " % the size of the original vertex buffer."); - System.out.println("---> Gzipped numTriangles are " + gzippedPercentageOfOriginalNumTrianglesSize + " % the size of the original vertex buffer."); - System.out.println(); - } + @Test + void triangulateVectorTile() throws IOException { + var path = Paths.get("../test/fixtures/amazon/5_5_11.pbf").toAbsolutePath(); + + var polygonConverter = new VectorTileConverter(path); + + Assert.isTrue(polygonConverter.getNumTrianglesPerPolygon().size() > 0); + Assert.isTrue(polygonConverter.getIndexBuffer().size() > 0); + + logVectorizationInformation(polygonConverter); + } + + @Test + void triangulateVectorTile2() throws IOException { + var path = Paths.get("../test/fixtures/omt/13_4266_5468.mvt").toAbsolutePath(); + var encodedTile = Files.readAllBytes(path); + + var polygonConverter = new VectorTileConverter(encodedTile); + + Assert.isTrue(polygonConverter.getNumTrianglesPerPolygon().size() > 0); + Assert.isTrue(polygonConverter.getIndexBuffer().size() > 0); + logVectorizationInformation(polygonConverter); + } + + @Test + void verifyPolygonTriangulation() { + Polygon polygon = mock(Polygon.class); + + when(polygon.getCoordinates()).thenReturn(getQuadraticPolygonCoordinates()); + var triangulatedPolygon = TriangulationUtils.triangulatePolygon(polygon); + + Assert.isTrue(triangulatedPolygon.getNumTrianglesPerPolygon() == 2); + Assert.isTrue(triangulatedPolygon.getIndexBuffer().size() == 6); + } + + @Test + void verifyMultiPolygonTriangulation() { + MultiPolygon multiPolygon = mock(MultiPolygon.class); + Polygon outerPolygon = mock(Polygon.class); + when(outerPolygon.getCoordinates()).thenReturn(getQuadraticPolygonCoordinates()); + + Polygon innerPolygon = mock(Polygon.class); + Coordinate[] innerPolygonCoordinates = new Coordinate[4]; + innerPolygonCoordinates[0] = new Coordinate(0.25, 0.25); + innerPolygonCoordinates[1] = new Coordinate(0.75, 0.25); + innerPolygonCoordinates[2] = new Coordinate(0.75, 0.75); + innerPolygonCoordinates[3] = new Coordinate(0.25, 0.75); + + when(innerPolygon.getCoordinates()).thenReturn(innerPolygonCoordinates); + + when(multiPolygon.getNumGeometries()).thenReturn(2); + when(multiPolygon.getGeometryN(0)).thenReturn(outerPolygon); + when(multiPolygon.getGeometryN(1)).thenReturn(innerPolygon); + + var triangulatedPolygon = TriangulationUtils.triangulatePolygonWithHoles(multiPolygon); + + Assert.isTrue(triangulatedPolygon.getNumTrianglesPerPolygon() == 8); + Assert.isTrue(triangulatedPolygon.getIndexBuffer().size() == 24); + } + + private Coordinate[] getQuadraticPolygonCoordinates() { + Coordinate[] coordinates = new Coordinate[4]; + coordinates[0] = new Coordinate(0, 0); + coordinates[1] = new Coordinate(1, 0); + coordinates[2] = new Coordinate(1, 1); + coordinates[3] = new Coordinate(0, 1); + return coordinates; + } + + private int getByteSize(int[] array) { + return 4 * array.length; + } + + private void logVectorizationInformation(VectorTileConverter vectorTileConverter) + throws IOException { + var indexBufferSize = + getByteSize(vectorTileConverter.getIndexBuffer().stream().mapToInt(i -> i).toArray()); + var encodedIndexBuffer = vectorTileConverter.getEncodedIndexBuffer(); + var gzippedIndexBuffer = vectorTileConverter.getGzippedIndexBuffer(); + + var encodedPercentageOfOriginalSize = + (double) encodedIndexBuffer.length / (double) indexBufferSize * 100; + var gzippedPercentageOfOriginalSize = + (double) gzippedIndexBuffer.length / (double) indexBufferSize * 100; + + System.out.println("------------ IndexBuffer result ------------"); + System.out.println("#### Byte size of integer index array: " + indexBufferSize); + System.out.println("#### Byte size of encoded index array: " + encodedIndexBuffer.length); + System.out.println("#### Byte size of gzipped index array: " + gzippedIndexBuffer.length); + System.out.println("------"); + System.out.println( + "#### Array length of index integer array: " + vectorTileConverter.getIndexBuffer().size()); + System.out.println( + "#### Array length of encoded index byte array: " + encodedIndexBuffer.length); + System.out.println( + "#### Array length of gzipped index byte array: " + gzippedIndexBuffer.length); + System.out.println( + "---> Encoded vertices are " + + round(encodedPercentageOfOriginalSize, 2) + + " % the size of the original vertex buffer."); + System.out.println( + "---> Gzipped vertices are " + + round(gzippedPercentageOfOriginalSize, 2) + + " % the size of the original vertex buffer."); + System.out.println(); + + var numTrianglesSize = + getByteSize( + vectorTileConverter.getNumTrianglesPerPolygon().stream().mapToInt(i -> i).toArray()); + var encodedNumTriangles = vectorTileConverter.getEncodedNumberOfTrianglesPerPolygon(); + var gzippedNumTriangles = vectorTileConverter.getGzippedNumberOfTrianglesPerPolygon(); + var percentageOfOriginalNumTrianglesSize = + (double) encodedNumTriangles.length / (double) numTrianglesSize * 100; + var gzippedPercentageOfOriginalNumTrianglesSize = + (double) gzippedNumTriangles.length / (double) numTrianglesSize * 100; + + System.out.println("------------ NumTriangles result ------------"); + System.out.println("#### Byte size of integer index array: " + numTrianglesSize); + System.out.println("#### Byte size of encoded index array: " + encodedNumTriangles.length); + System.out.println("#### Byte size of gzipped index array: " + gzippedNumTriangles.length); + System.out.println("------"); + System.out.println( + "#### Array length of index integer array: " + + vectorTileConverter.getNumTrianglesPerPolygon().size()); + System.out.println( + "#### Array length of encoded index byte array: " + encodedNumTriangles.length); + System.out.println( + "#### Array length of gzipped index byte array: " + gzippedNumTriangles.length); + System.out.println( + "---> Encoded numTriangles are " + + round(percentageOfOriginalNumTrianglesSize, 2) + + " % the size of the original vertex buffer."); + System.out.println( + "---> Gzipped numTriangles are " + + round(gzippedPercentageOfOriginalNumTrianglesSize, 2) + + " % the size of the original vertex buffer."); + System.out.println(); + } + + public static double round(double value, int scale) { + return Math.round(value * Math.pow(10, scale)) / Math.pow(10, scale); + } } From ea28866a4b4f0c48ddc352d8ae2038b358b2f619 Mon Sep 17 00:00:00 2001 From: Felix Date: Wed, 13 Nov 2024 08:57:39 +0100 Subject: [PATCH 5/8] Add option to triangulate polygons when converting a Mvt or a geometry column - Add unit tests --- .../jmh/java/com/mlt/OmtDecoderBenchmark.java | 2 +- java/src/main/java/com/mlt/cli/Encode.java | 2 +- .../java/com/mlt/converter/MltConverter.java | 5 +- .../vectorized/VectorizedGeometryDecoder.java | 23 +++- .../com/mlt/converter/MltConverterTest.java | 32 +++++- .../encodings/GeometryEncodingTest.java | 100 ++++++++++++++++++ .../com/mlt/decoder/MltDecoderBenchmark.java | 2 +- .../java/com/mlt/decoder/MltDecoderTest.java | 2 +- 8 files changed, 153 insertions(+), 15 deletions(-) create mode 100644 java/src/test/java/com/mlt/converter/encodings/GeometryEncodingTest.java diff --git a/java/src/jmh/java/com/mlt/OmtDecoderBenchmark.java b/java/src/jmh/java/com/mlt/OmtDecoderBenchmark.java index ea9c0662..6e288536 100644 --- a/java/src/jmh/java/com/mlt/OmtDecoderBenchmark.java +++ b/java/src/jmh/java/com/mlt/OmtDecoderBenchmark.java @@ -104,7 +104,7 @@ private void encodeTile(int z, int x, int y) throws IOException { optimization); var encodedMltTile = MltConverter.convertMvt( - encodedMvtTile.getRight(), new ConversionConfig(true, true, optimizations), metadata); + encodedMvtTile.getRight(), new ConversionConfig(true, true, optimizations), metadata, false); encodedMltTiles.put(z, encodedMltTile); } diff --git a/java/src/main/java/com/mlt/cli/Encode.java b/java/src/main/java/com/mlt/cli/Encode.java index a9117803..283734b8 100644 --- a/java/src/main/java/com/mlt/cli/Encode.java +++ b/java/src/main/java/com/mlt/cli/Encode.java @@ -237,7 +237,7 @@ public static void main(String[] args) { var conversionConfig = new ConversionConfig(includeIds, useAdvancedEncodingSchemes, optimizations); Timer timer = new Timer(); - var mlTile = MltConverter.convertMvt(decodedMvTile, conversionConfig, tileMetadata); + var mlTile = MltConverter.convertMvt(decodedMvTile, conversionConfig, tileMetadata, false); timer.stop("encoding"); if (willOutput) { Path outputPath = null; diff --git a/java/src/main/java/com/mlt/converter/MltConverter.java b/java/src/main/java/com/mlt/converter/MltConverter.java index 5d3dad22..6f9e0c11 100644 --- a/java/src/main/java/com/mlt/converter/MltConverter.java +++ b/java/src/main/java/com/mlt/converter/MltConverter.java @@ -170,7 +170,8 @@ public static MltTilesetMetadata.TileSetMetadata createTilesetMetadata( public static byte[] convertMvt( MapboxVectorTile mvt, ConversionConfig config, - MltTilesetMetadata.TileSetMetadata tilesetMetadata) + MltTilesetMetadata.TileSetMetadata tilesetMetadata, + boolean triangulatePolygons) throws IOException { var physicalLevelTechnique = config.useAdvancedEncodingSchemes() @@ -199,7 +200,7 @@ public static byte[] convertMvt( /* Encode geometry and property columns */ var encodedGeometryColumn = GeometryEncoder.encodeGeometryColumn( - geometries, physicalLevelTechnique, new ArrayList<>(), false); + geometries, physicalLevelTechnique, new ArrayList<>(), triangulatePolygons); var encodedGeometryFieldMetadata = EncodingUtils.encodeVarints(new long[] {encodedGeometryColumn.getLeft()}, false, false); var propertyColumns = filterPropertyColumns(featureTableMetadata); diff --git a/java/src/main/java/com/mlt/decoder/vectorized/VectorizedGeometryDecoder.java b/java/src/main/java/com/mlt/decoder/vectorized/VectorizedGeometryDecoder.java index 735b4531..ebb11ce7 100644 --- a/java/src/main/java/com/mlt/decoder/vectorized/VectorizedGeometryDecoder.java +++ b/java/src/main/java/com/mlt/decoder/vectorized/VectorizedGeometryDecoder.java @@ -1,8 +1,6 @@ package com.mlt.decoder.vectorized; -import com.mlt.metadata.stream.DictionaryType; -import com.mlt.metadata.stream.MortonEncodedStreamMetadata; -import com.mlt.metadata.stream.StreamMetadataDecoder; +import com.mlt.metadata.stream.*; import com.mlt.vector.VectorType; import com.mlt.vector.geometry.GeometryVector; import com.mlt.vector.geometry.TopologyVector; @@ -19,6 +17,8 @@ public record GeometryColumn( IntBuffer numRings, IntBuffer vertexOffsets, IntBuffer vertexBuffer, + Optional indexBuffer, + Optional numTrianglesPerPolygonBuffer, Optional mortonSettings) {} private VectorizedGeometryDecoder() {} @@ -35,6 +35,8 @@ public static VectorizedGeometryDecoder.GeometryColumn decodeGeometryColumn( IntBuffer numRings = null; IntBuffer vertexOffsets = null; IntBuffer vertexBuffer = null; + Optional indexBuffer = Optional.empty(); + Optional numTrianglesPerPolygonBuffer = Optional.empty(); Optional mortonSettings = Optional.empty(); for (var i = 0; i < numStreams - 1; i++) { var geometryStreamMetadata = StreamMetadataDecoder.decode(tile, offset); @@ -61,8 +63,17 @@ public static VectorizedGeometryDecoder.GeometryColumn decodeGeometryColumn( } break; case OFFSET: - vertexOffsets = - VectorizedIntegerDecoder.decodeIntStream(tile, offset, geometryStreamMetadata, false); + switch (geometryStreamMetadata.logicalStreamType().offsetType()){ + case INDEX: + if (indexBuffer.isEmpty()) { + indexBuffer = Optional.of(VectorizedIntegerDecoder.decodeIntStream(tile, offset, geometryStreamMetadata, false)); + }else { + numTrianglesPerPolygonBuffer = Optional.of(VectorizedIntegerDecoder.decodeIntStream(tile, offset, geometryStreamMetadata,false)); + } + break; + default: + vertexOffsets = VectorizedIntegerDecoder.decodeIntStream(tile, offset, geometryStreamMetadata, false); + } break; case DATA: if (DictionaryType.VERTEX.equals( @@ -91,6 +102,8 @@ public static VectorizedGeometryDecoder.GeometryColumn decodeGeometryColumn( numRings, vertexOffsets, vertexBuffer, + indexBuffer, + numTrianglesPerPolygonBuffer, mortonSettings); } diff --git a/java/src/test/java/com/mlt/converter/MltConverterTest.java b/java/src/test/java/com/mlt/converter/MltConverterTest.java index f39b6cea..8454b1c9 100644 --- a/java/src/test/java/com/mlt/converter/MltConverterTest.java +++ b/java/src/test/java/com/mlt/converter/MltConverterTest.java @@ -21,6 +21,7 @@ import java.util.stream.Stream; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.locationtech.jts.util.Assert; public class MltConverterTest { ; @@ -187,7 +188,7 @@ private static void runOmtTest2(String tile) throws IOException { // TODO: fix -> either add columMappings per layer or global like when creating the scheme var optimizations = Map.of("place", optimization, "water_name", optimization); var conversionConfig = new ConversionConfig(true, true, optimizations); - var mlTile = MltConverter.convertMvt(mvTile, conversionConfig, tileMetadata); + var mlTile = MltConverter.convertMvt(mvTile, conversionConfig, tileMetadata, false); var decodedMlTile = MltDecoder.decodeMlTile(mlTile, tileMetadata); MltDecoderTest.compareTilesSequential(decodedMlTile, mvTile); @@ -304,7 +305,7 @@ private static void runOmtTest(String tileId) throws IOException { // TODO: fix -> either add columMappings per layer or global like when creating the scheme var optimizations = Map.of("place", optimization, "water_name", optimization); var conversionConfig = new ConversionConfig(true, true, optimizations); - var mlTile = MltConverter.convertMvt(mvTile, conversionConfig, tileMetadata); + var mlTile = MltConverter.convertMvt(mvTile, conversionConfig, tileMetadata, false); // var decodedMlTile = MltDecoder.decodeMlTile(mlTile, tileMetadata); // MltDecoderTest.compareTiles(decodedMlTile, mvTile); @@ -361,7 +362,7 @@ private static void runOmtTests(int zoom, int minX, int maxX, int minY, int maxY "aerodrome_label", optimization); var conversionConfig = new ConversionConfig(true, true, optimizations); - var mlTile = MltConverter.convertMvt(decodedMvTile, conversionConfig, tileMetadata); + var mlTile = MltConverter.convertMvt(decodedMvTile, conversionConfig, tileMetadata, false); // var decodedMlTile = MltDecoder.decodeMlTile(mlTile, tileMetadata); // MltDecoderTest.compareTiles(decodedMlTile, decodedMvTile); @@ -428,6 +429,29 @@ public void convert_BingMaps_Z7Tiles() throws IOException { runBingTests(fileNames); } + @Test + public void convertMapTileAndTriangulatePolygons() throws IOException { // TODO: ADD test to validate indexBuffer + var mvtFilePath = Paths.get(TestConstants.BING_MVT_PATH, "4-8-5" + ".mvt"); + var decodedMvTile = MvtUtils.decodeMvt(mvtFilePath); + var columnMapping = new ColumnMapping("name", ":", true); + var columnMappings = Optional.of(List.of(columnMapping)); + var optimization = + new FeatureTableOptimizations(false, false, columnMappings); + var optimizations = Map.of("place", optimization, "water_name", optimization); + var conversionConfig = new ConversionConfig(true, true, optimizations); + + var tileMetadata = + MltConverter.createTilesetMetadata( + decodedMvTile, Optional.of(List.of(columnMapping)), true); + + // converted to MLT + var mlTile = MltConverter.convertMvt(decodedMvTile, conversionConfig, tileMetadata, false); + + var decodedMlTile = MltDecoder.decodeMlTile(mlTile, tileMetadata); + + Assert.isTrue(decodedMlTile.layers().size() > 0); + } + private void runBingTests(List fileNames) throws IOException { var compressionRatios = 0d; for (var fileName : fileNames) { @@ -457,7 +481,7 @@ private double runBingTest(String tileId) throws IOException { // TODO: fix -> either add columMappings per layer or global like when creating the scheme var optimizations = Map.of("place", optimization, "water_name", optimization); var conversionConfig = new ConversionConfig(true, true, optimizations); - var mlTile = MltConverter.convertMvt(decodedMvTile, conversionConfig, tileMetadata); + var mlTile = MltConverter.convertMvt(decodedMvTile, conversionConfig, tileMetadata, false); var decodedMlTile = MltDecoder.decodeMlTile(mlTile, tileMetadata); MltDecoderTest.compareTilesSequential(decodedMlTile, decodedMvTile); diff --git a/java/src/test/java/com/mlt/converter/encodings/GeometryEncodingTest.java b/java/src/test/java/com/mlt/converter/encodings/GeometryEncodingTest.java new file mode 100644 index 00000000..c23f19f5 --- /dev/null +++ b/java/src/test/java/com/mlt/converter/encodings/GeometryEncodingTest.java @@ -0,0 +1,100 @@ +package com.mlt.converter.encodings; +import com.mlt.converter.mvt.MvtUtils; +import com.mlt.converter.triangulation.TriangulationUtils; +import com.mlt.decoder.vectorized.VectorizedGeometryDecoder; +import com.mlt.metadata.stream.PhysicalLevelTechnique; +import me.lemire.integercompression.IntWrapper; +import org.locationtech.jts.geom.Geometry; + + +import org.junit.jupiter.api.Test; +import org.locationtech.jts.geom.MultiPolygon; +import org.locationtech.jts.geom.Polygon; +import org.locationtech.jts.util.Assert; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; + +public class GeometryEncodingTest { + Path mvtFilePath = Paths.get(com.mlt.test.constants.TestConstants.BING_MVT_PATH, "4-8-5" + ".mvt"); + + PhysicalLevelTechnique physicalLevelTechnique = PhysicalLevelTechnique.FAST_PFOR; + + @Test + public void testTriangulatedGeometryEncodingForTile() throws IOException { + var decodedMvTile = MvtUtils.decodeMvt(mvtFilePath); + + var geometries = new ArrayList(); + var featureIds = new ArrayList(); + + decodedMvTile.layers().forEach(layer -> { + layer.features().forEach(feature -> { + geometries.add(feature.geometry()); + featureIds.add(feature.id()); + }); + }); + + var encodedGeometryColumn = GeometryEncoder.encodeGeometryColumn(geometries, physicalLevelTechnique, featureIds, true); + var decodedGeometryColumn = VectorizedGeometryDecoder.decodeGeometryColumn(encodedGeometryColumn.getMiddle(), encodedGeometryColumn.getLeft(), new IntWrapper(0)); + + Assert.isTrue(decodedGeometryColumn.indexBuffer().isPresent()); + Assert.isTrue(decodedGeometryColumn.numTrianglesPerPolygonBuffer().isPresent()); + } + + @Test + public void testTriangulateGeometryColumnForPolygonLayer() throws IOException { + var decodedMvTile = MvtUtils.decodeMvt(mvtFilePath); + + var geometries = new ArrayList(); + var featureIds = new ArrayList(); + var polygonFeature = decodedMvTile.layers().get(5).features().get(0); + geometries.add(polygonFeature.geometry()); + featureIds.add(polygonFeature.id()); + + var encodedGeometryColumn = GeometryEncoder.encodeGeometryColumn(geometries, physicalLevelTechnique, featureIds, true); + var decodedGeometryColumn = VectorizedGeometryDecoder.decodeGeometryColumn(encodedGeometryColumn.getMiddle(), encodedGeometryColumn.getLeft(), new IntWrapper(0)); + + var expectedTriangulatedPolygon = TriangulationUtils.triangulatePolygon((Polygon) polygonFeature.geometry()); + var indexBuffer = expectedTriangulatedPolygon.getIndexBuffer(); + + var expectedIndexBuffer = new int[indexBuffer.size()]; + for (int i = 0; i < indexBuffer.size(); i++) { + expectedIndexBuffer[i] = indexBuffer.get(i); + } + + Assert.isTrue(decodedGeometryColumn.indexBuffer().isPresent()); + Assert.isTrue(decodedGeometryColumn.numTrianglesPerPolygonBuffer().isPresent()); + Assert.equals(decodedGeometryColumn.numTrianglesPerPolygonBuffer().get().array()[0], expectedTriangulatedPolygon.getNumTrianglesPerPolygon()); + Assert.isTrue(Arrays.equals(decodedGeometryColumn.indexBuffer().get().array(), expectedIndexBuffer)); + } + + @Test + public void testTriangulateGeometryColumnForMultiPolygonLayer() throws IOException { + var decodedMvTile = MvtUtils.decodeMvt(mvtFilePath); + + var geometries = new ArrayList(); + var featureIds = new ArrayList(); + var polygonFeature = decodedMvTile.layers().get(0).features().get(0); + geometries.add(polygonFeature.geometry()); + featureIds.add(polygonFeature.id()); + + var encodedGeometryColumn = GeometryEncoder.encodeGeometryColumn(geometries, physicalLevelTechnique, featureIds, true); + var decodedGeometryColumn = VectorizedGeometryDecoder.decodeGeometryColumn(encodedGeometryColumn.getMiddle(), encodedGeometryColumn.getLeft(), new IntWrapper(0)); + + var expectedTriangulatedMultiPolygon = TriangulationUtils.triangulatePolygonWithHoles((MultiPolygon) polygonFeature.geometry()); + var indexBuffer = expectedTriangulatedMultiPolygon.getIndexBuffer(); + + var expectedIndexBuffer = new int[indexBuffer.size()]; + for (int i = 0; i < indexBuffer.size(); i++) { + expectedIndexBuffer[i] = indexBuffer.get(i); + } + + Assert.isTrue(decodedGeometryColumn.indexBuffer().isPresent()); + Assert.isTrue(decodedGeometryColumn.numTrianglesPerPolygonBuffer().isPresent()); + Assert.equals(decodedGeometryColumn.numTrianglesPerPolygonBuffer().get().array()[0], expectedTriangulatedMultiPolygon.getNumTrianglesPerPolygon()); + Assert.isTrue(Arrays.equals(decodedGeometryColumn.indexBuffer().get().array(), expectedIndexBuffer)); + } +} diff --git a/java/src/test/java/com/mlt/decoder/MltDecoderBenchmark.java b/java/src/test/java/com/mlt/decoder/MltDecoderBenchmark.java index 682545ce..0f8b9bf0 100644 --- a/java/src/test/java/com/mlt/decoder/MltDecoderBenchmark.java +++ b/java/src/test/java/com/mlt/decoder/MltDecoderBenchmark.java @@ -193,7 +193,7 @@ private void benchmarkDecoding(String tileId) throws IOException { optimization); var mlTile = MltConverter.convertMvt( - mvTile, new ConversionConfig(true, true, optimizations), tileMetadata); + mvTile, new ConversionConfig(true, true, optimizations), tileMetadata, false); var mltTimeElapsed = 0L; for (int i = 0; i <= 200; i++) { diff --git a/java/src/test/java/com/mlt/decoder/MltDecoderTest.java b/java/src/test/java/com/mlt/decoder/MltDecoderTest.java index 0532dc09..44828faf 100644 --- a/java/src/test/java/com/mlt/decoder/MltDecoderTest.java +++ b/java/src/test/java/com/mlt/decoder/MltDecoderTest.java @@ -322,7 +322,7 @@ private void testTile( optimization); var mlTile = MltConverter.convertMvt( - mvTile, new ConversionConfig(true, true, optimizations), tileMetadata); + mvTile, new ConversionConfig(true, true, optimizations), tileMetadata, false); decodeAndCompare.apply(mlTile, tileMetadata, mvTile); From 2bc2ee77e4cfc8fedfe7d53c193240bec21fac46 Mon Sep 17 00:00:00 2001 From: Felix Date: Wed, 13 Nov 2024 09:02:44 +0100 Subject: [PATCH 6/8] Revert changes in build.gradle --- java/build.gradle | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/java/build.gradle b/java/build.gradle index 5ad885d9..1d9c671c 100644 --- a/java/build.gradle +++ b/java/build.gradle @@ -66,15 +66,15 @@ spotless { } } -//task compileWrapper(type: Exec) { -// doFirst { -// if (System.properties['os.name'].toLowerCase().contains('windows')) { -// executable "./resources/compile-windows.bat" -// } else { -// executable "./resources/compile" -// } -// } -//} +task compileWrapper(type: Exec) { + doFirst { + if (System.properties['os.name'].toLowerCase().contains('windows')) { + executable "./resources/compile-windows.bat" + } else { + executable "./resources/compile" + } + } +} gradle.projectsEvaluated { tasks.withType(JavaCompile) { @@ -82,7 +82,7 @@ gradle.projectsEvaluated { } } -//compileJava.dependsOn compileWrapper +compileJava.dependsOn compileWrapper ["encode", "decode", "meta"].each { name -> From 411e9fbd80e6d4099d24828b0f58a511e684180e Mon Sep 17 00:00:00 2001 From: Felix Date: Wed, 13 Nov 2024 10:59:24 +0100 Subject: [PATCH 7/8] Adjust MltConverter and GeometryEncoder after merge --- java/src/jmh/java/com/mlt/BenchmarkUtils.java | 5 +- .../java/com/mlt/converter/MltConverter.java | 13 +- .../converter/encodings/GeometryEncoder.java | 58 +++--- .../vectorized/VectorizedGeometryDecoder.java | 14 +- .../mlt/benchmarks/CompressionBenchmarks.java | 2 +- .../com/mlt/converter/MltConverterTest.java | 1 - .../encodings/GeometryEncodingTest.java | 185 ++++++++++-------- .../java/com/mlt/decoder/MltDecoderTest.java | 4 +- 8 files changed, 158 insertions(+), 124 deletions(-) diff --git a/java/src/jmh/java/com/mlt/BenchmarkUtils.java b/java/src/jmh/java/com/mlt/BenchmarkUtils.java index 07917840..82ab35c2 100644 --- a/java/src/jmh/java/com/mlt/BenchmarkUtils.java +++ b/java/src/jmh/java/com/mlt/BenchmarkUtils.java @@ -49,7 +49,10 @@ public static void encodeTile( .collect(Collectors.toMap(l -> l, l -> optimization)); var encodedMltTile = MltConverter.convertMvt( - encodedMvtTile.getRight(), new ConversionConfig(true, true, optimizations), metadata); + encodedMvtTile.getRight(), + new ConversionConfig(true, true, optimizations), + metadata, + false); encodedMltTiles.put(z, encodedMltTile); } diff --git a/java/src/main/java/com/mlt/converter/MltConverter.java b/java/src/main/java/com/mlt/converter/MltConverter.java index 7f1a2088..d3092ef9 100644 --- a/java/src/main/java/com/mlt/converter/MltConverter.java +++ b/java/src/main/java/com/mlt/converter/MltConverter.java @@ -193,7 +193,12 @@ public static byte[] convertMvt( var result = sortFeaturesAndEncodeGeometryColumn( - config, featureTableOptimizations, mvtFeatures, mvtFeatures, physicalLevelTechnique); + config, + featureTableOptimizations, + mvtFeatures, + mvtFeatures, + physicalLevelTechnique, + triangulatePolygons); var sortedFeatures = result.getLeft(); var encodedGeometryColumn = result.getRight(); var encodedGeometryFieldMetadata = @@ -267,7 +272,8 @@ private static byte[] encodePropertyColumns( FeatureTableOptimizations featureTableOptimizations, List sortedFeatures, List mvtFeatures, - PhysicalLevelTechnique physicalLevelTechnique) { + PhysicalLevelTechnique physicalLevelTechnique, + Boolean triangulatePolygons) { /* * Following simple strategy is currently used for ordering the features when sorting is enabled: * - if id column is present and ids should not be reassigned -> sort id column @@ -295,7 +301,8 @@ private static byte[] encodePropertyColumns( new GeometryEncoder.SortSettings( isColumnSortable && featureTableOptimizations.allowIdRegeneration(), ids); var encodedGeometryColumn = - GeometryEncoder.encodeGeometryColumn(geometries, physicalLevelTechnique, sortSettings); + GeometryEncoder.encodeGeometryColumn( + geometries, physicalLevelTechnique, sortSettings, triangulatePolygons); if (encodedGeometryColumn.geometryColumnSorted()) { sortedFeatures = diff --git a/java/src/main/java/com/mlt/converter/encodings/GeometryEncoder.java b/java/src/main/java/com/mlt/converter/encodings/GeometryEncoder.java index 861513e1..d499ae31 100644 --- a/java/src/main/java/com/mlt/converter/encodings/GeometryEncoder.java +++ b/java/src/main/java/com/mlt/converter/encodings/GeometryEncoder.java @@ -12,7 +12,6 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; import org.apache.commons.lang3.ArrayUtils; -import org.apache.commons.lang3.tuple.Triple; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.GeometryFactory; import org.locationtech.jts.geom.LineString; @@ -112,6 +111,11 @@ public static EncodedGeometryColumn encodeGeometryColumn( var vertices = flatPolygon(polygon, numParts, numRings); vertexBuffer.addAll(vertices); } + if (triangulatePolygons) { + var triangulatedPolygon = new VectorTileConverter(multiPolygon); + indexBuffer.addAll(triangulatedPolygon.getIndexBuffer()); + numTrianglesPerPolygon.addAll(triangulatedPolygon.getNumTrianglesPerPolygon()); + } break; } case Geometry.TYPENAME_MULTIPOINT: @@ -126,11 +130,6 @@ public static EncodedGeometryColumn encodeGeometryColumn( var y = (int) point.getY(); vertexBuffer.add(new Vertex(x, y)); } - if (triangulatePolygons) { - var triangulatedPolygon = new VectorTileConverter(multiPolygon); - indexBuffer.addAll(triangulatedPolygon.getIndexBuffer()); - numTrianglesPerPolygon.addAll(triangulatedPolygon.getNumTrianglesPerPolygon()); - } break; } default: @@ -281,11 +280,12 @@ public static EncodedGeometryColumn encodeGeometryColumn( Arrays.stream(zigZagDeltaVertexBuffer).boxed().collect(Collectors.toList()), physicalLevelTechnique); - return new EncodedGeometryColumn( - numStreams + 1, - ArrayUtils.addAll(encodedTopologyStreams, encodedVertexBufferStream), - maxVertexValue, - geometryColumnSorted); + var encodedGeometryColumn = new EncodedGeometryColumn( + numStreams + 1, + ArrayUtils.addAll(encodedTopologyStreams, encodedVertexBufferStream), + maxVertexValue, + geometryColumnSorted); + return triangulatePolygons ? buildTriangulatedEncodedGeometryColumn(encodedGeometryColumn, encodedIndexBuffer, encodedNumTrianglesBuffer) : encodedGeometryColumn; } else if (dictionaryEncodedSize < plainVertexBufferSize && dictionaryEncodedSize <= mortonDictionaryEncodedSize) { var encodedVertexOffsetStream = @@ -299,13 +299,12 @@ public static EncodedGeometryColumn encodeGeometryColumn( encodeVertexBuffer( Arrays.stream(zigZagDeltaVertexDictionary).boxed().collect(Collectors.toList()), physicalLevelTechnique); - - return new EncodedGeometryColumn( - numStreams + 2, - CollectionUtils.concatByteArrays( - encodedTopologyStreams, encodedVertexOffsetStream, encodedVertexDictionaryStream), + var encodedGeometryColumn = new EncodedGeometryColumn( + numStreams + 2, CollectionUtils.concatByteArrays(encodedTopologyStreams, encodedVertexOffsetStream, encodedVertexDictionaryStream), maxVertexValue, false); + + return triangulatePolygons ? buildTriangulatedEncodedGeometryColumn(encodedGeometryColumn, encodedIndexBuffer, encodedNumTrianglesBuffer) : encodedGeometryColumn; } else { var encodedMortonVertexOffsetStream = IntegerEncoder.encodeIntStream( @@ -322,14 +321,15 @@ public static EncodedGeometryColumn encodeGeometryColumn( zOrderCurve.coordinateShift(), physicalLevelTechnique); - return new EncodedGeometryColumn( - numStreams + 2, - CollectionUtils.concatByteArrays( - encodedTopologyStreams, - encodedMortonVertexOffsetStream, - encodedMortonEncodedVertexDictionaryStream), - maxVertexValue, - geometryColumnSorted); + var encodedGeometryColumn = new EncodedGeometryColumn( + numStreams + 2, + CollectionUtils.concatByteArrays(encodedTopologyStreams, + encodedMortonVertexOffsetStream, + encodedMortonEncodedVertexDictionaryStream), + maxVertexValue, + geometryColumnSorted); + + return triangulatePolygons ? buildTriangulatedEncodedGeometryColumn(encodedGeometryColumn, encodedIndexBuffer, encodedNumTrianglesBuffer) : encodedGeometryColumn; } } @@ -457,14 +457,10 @@ private static byte[] encodeVertexBuffer( return ArrayUtils.addAll(encodedMetadata, encodedValues); } - private static Triple buildTriangulatedTriple( - Triple triple, + private static EncodedGeometryColumn buildTriangulatedEncodedGeometryColumn( + EncodedGeometryColumn encodedGeometryColumn, byte[] encodedIndexBuffer, byte[] encodedNumTrianglesPerPolygon) { - return Triple.of( - triple.getLeft() + 2, - CollectionUtils.concatByteArrays( - triple.getMiddle(), encodedIndexBuffer, encodedNumTrianglesPerPolygon), - triple.getRight()); + return new EncodedGeometryColumn(encodedGeometryColumn.numStreams + 2, CollectionUtils.concatByteArrays(encodedGeometryColumn.encodedValues, encodedIndexBuffer, encodedNumTrianglesPerPolygon), encodedGeometryColumn.maxVertexValue, encodedGeometryColumn.geometryColumnSorted); } } diff --git a/java/src/main/java/com/mlt/decoder/vectorized/VectorizedGeometryDecoder.java b/java/src/main/java/com/mlt/decoder/vectorized/VectorizedGeometryDecoder.java index ebb11ce7..5d6db55f 100644 --- a/java/src/main/java/com/mlt/decoder/vectorized/VectorizedGeometryDecoder.java +++ b/java/src/main/java/com/mlt/decoder/vectorized/VectorizedGeometryDecoder.java @@ -63,12 +63,18 @@ public static VectorizedGeometryDecoder.GeometryColumn decodeGeometryColumn( } break; case OFFSET: - switch (geometryStreamMetadata.logicalStreamType().offsetType()){ + switch (geometryStreamMetadata.logicalStreamType().offsetType()) { case INDEX: if (indexBuffer.isEmpty()) { - indexBuffer = Optional.of(VectorizedIntegerDecoder.decodeIntStream(tile, offset, geometryStreamMetadata, false)); - }else { - numTrianglesPerPolygonBuffer = Optional.of(VectorizedIntegerDecoder.decodeIntStream(tile, offset, geometryStreamMetadata,false)); + indexBuffer = + Optional.of( + VectorizedIntegerDecoder.decodeIntStream( + tile, offset, geometryStreamMetadata, false)); + } else { + numTrianglesPerPolygonBuffer = + Optional.of( + VectorizedIntegerDecoder.decodeIntStream( + tile, offset, geometryStreamMetadata, false)); } break; default: diff --git a/java/src/test/java/com/mlt/benchmarks/CompressionBenchmarks.java b/java/src/test/java/com/mlt/benchmarks/CompressionBenchmarks.java index 3f24f722..86973c80 100644 --- a/java/src/test/java/com/mlt/benchmarks/CompressionBenchmarks.java +++ b/java/src/test/java/com/mlt/benchmarks/CompressionBenchmarks.java @@ -143,7 +143,7 @@ private static Pair getBenchmarksAndVerifyTiles( var mlTile = MltConverter.convertMvt( - mvTile, new ConversionConfig(true, true, optimizations), tileMetadata); + mvTile, new ConversionConfig(true, true, optimizations), tileMetadata, false); if (reassignableLayers.isEmpty()) { /* Only test when the ids are not reassigned since it is verified based on the other tests */ diff --git a/java/src/test/java/com/mlt/converter/MltConverterTest.java b/java/src/test/java/com/mlt/converter/MltConverterTest.java index 91561008..6f73fb88 100644 --- a/java/src/test/java/com/mlt/converter/MltConverterTest.java +++ b/java/src/test/java/com/mlt/converter/MltConverterTest.java @@ -16,7 +16,6 @@ import java.util.stream.Stream; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.locationtech.jts.util.Assert; public class MltConverterTest { diff --git a/java/src/test/java/com/mlt/converter/encodings/GeometryEncodingTest.java b/java/src/test/java/com/mlt/converter/encodings/GeometryEncodingTest.java index c23f19f5..87d21023 100644 --- a/java/src/test/java/com/mlt/converter/encodings/GeometryEncodingTest.java +++ b/java/src/test/java/com/mlt/converter/encodings/GeometryEncodingTest.java @@ -1,100 +1,123 @@ package com.mlt.converter.encodings; + +import com.mlt.TestSettings; import com.mlt.converter.mvt.MvtUtils; import com.mlt.converter.triangulation.TriangulationUtils; import com.mlt.decoder.vectorized.VectorizedGeometryDecoder; import com.mlt.metadata.stream.PhysicalLevelTechnique; -import me.lemire.integercompression.IntWrapper; -import org.locationtech.jts.geom.Geometry; - - -import org.junit.jupiter.api.Test; -import org.locationtech.jts.geom.MultiPolygon; -import org.locationtech.jts.geom.Polygon; -import org.locationtech.jts.util.Assert; - import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; +import me.lemire.integercompression.IntWrapper; +import org.junit.jupiter.api.Test; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.MultiPolygon; +import org.locationtech.jts.geom.Polygon; +import org.locationtech.jts.util.Assert; public class GeometryEncodingTest { - Path mvtFilePath = Paths.get(com.mlt.test.constants.TestConstants.BING_MVT_PATH, "4-8-5" + ".mvt"); - - PhysicalLevelTechnique physicalLevelTechnique = PhysicalLevelTechnique.FAST_PFOR; - - @Test - public void testTriangulatedGeometryEncodingForTile() throws IOException { - var decodedMvTile = MvtUtils.decodeMvt(mvtFilePath); - - var geometries = new ArrayList(); - var featureIds = new ArrayList(); - - decodedMvTile.layers().forEach(layer -> { - layer.features().forEach(feature -> { - geometries.add(feature.geometry()); - featureIds.add(feature.id()); + Path mvtFilePath = + Paths.get(TestSettings.BING_MVT_PATH, "4-8-5" + ".mvt"); + + PhysicalLevelTechnique physicalLevelTechnique = PhysicalLevelTechnique.FAST_PFOR; + + @Test + public void testTriangulatedGeometryEncodingForTile() throws IOException { + var decodedMvTile = MvtUtils.decodeMvt(mvtFilePath); + + var geometries = new ArrayList(); + var featureIds = new ArrayList(); + + decodedMvTile + .layers() + .forEach( + layer -> { + layer + .features() + .forEach( + feature -> { + geometries.add(feature.geometry()); + featureIds.add(feature.id()); + }); }); - }); - var encodedGeometryColumn = GeometryEncoder.encodeGeometryColumn(geometries, physicalLevelTechnique, featureIds, true); - var decodedGeometryColumn = VectorizedGeometryDecoder.decodeGeometryColumn(encodedGeometryColumn.getMiddle(), encodedGeometryColumn.getLeft(), new IntWrapper(0)); - - Assert.isTrue(decodedGeometryColumn.indexBuffer().isPresent()); - Assert.isTrue(decodedGeometryColumn.numTrianglesPerPolygonBuffer().isPresent()); + var sortSettings = new GeometryEncoder.SortSettings(false, featureIds); + var encodedGeometryColumn = + GeometryEncoder.encodeGeometryColumn(geometries, physicalLevelTechnique, sortSettings, true); + var decodedGeometryColumn = + VectorizedGeometryDecoder.decodeGeometryColumn(encodedGeometryColumn.encodedValues(), encodedGeometryColumn.numStreams(), new IntWrapper(0)); + + Assert.isTrue(decodedGeometryColumn.indexBuffer().isPresent()); + Assert.isTrue(decodedGeometryColumn.numTrianglesPerPolygonBuffer().isPresent()); + } + + @Test + public void testTriangulateGeometryColumnForPolygonLayer() throws IOException { + var decodedMvTile = MvtUtils.decodeMvt(mvtFilePath); + + var geometries = new ArrayList(); + var featureIds = new ArrayList(); + var polygonFeature = decodedMvTile.layers().get(5).features().get(0); + geometries.add(polygonFeature.geometry()); + featureIds.add(polygonFeature.id()); + + var sortSettings = new GeometryEncoder.SortSettings(false, featureIds); + var encodedGeometryColumn = + GeometryEncoder.encodeGeometryColumn(geometries, physicalLevelTechnique, sortSettings, true); + var decodedGeometryColumn = + VectorizedGeometryDecoder.decodeGeometryColumn(encodedGeometryColumn.encodedValues(), encodedGeometryColumn.numStreams(), new IntWrapper(0)); + + var expectedTriangulatedPolygon = + TriangulationUtils.triangulatePolygon((Polygon) polygonFeature.geometry()); + var indexBuffer = expectedTriangulatedPolygon.getIndexBuffer(); + + var expectedIndexBuffer = new int[indexBuffer.size()]; + for (int i = 0; i < indexBuffer.size(); i++) { + expectedIndexBuffer[i] = indexBuffer.get(i); } - @Test - public void testTriangulateGeometryColumnForPolygonLayer() throws IOException { - var decodedMvTile = MvtUtils.decodeMvt(mvtFilePath); - - var geometries = new ArrayList(); - var featureIds = new ArrayList(); - var polygonFeature = decodedMvTile.layers().get(5).features().get(0); - geometries.add(polygonFeature.geometry()); - featureIds.add(polygonFeature.id()); - - var encodedGeometryColumn = GeometryEncoder.encodeGeometryColumn(geometries, physicalLevelTechnique, featureIds, true); - var decodedGeometryColumn = VectorizedGeometryDecoder.decodeGeometryColumn(encodedGeometryColumn.getMiddle(), encodedGeometryColumn.getLeft(), new IntWrapper(0)); - - var expectedTriangulatedPolygon = TriangulationUtils.triangulatePolygon((Polygon) polygonFeature.geometry()); - var indexBuffer = expectedTriangulatedPolygon.getIndexBuffer(); - - var expectedIndexBuffer = new int[indexBuffer.size()]; - for (int i = 0; i < indexBuffer.size(); i++) { - expectedIndexBuffer[i] = indexBuffer.get(i); - } - - Assert.isTrue(decodedGeometryColumn.indexBuffer().isPresent()); - Assert.isTrue(decodedGeometryColumn.numTrianglesPerPolygonBuffer().isPresent()); - Assert.equals(decodedGeometryColumn.numTrianglesPerPolygonBuffer().get().array()[0], expectedTriangulatedPolygon.getNumTrianglesPerPolygon()); - Assert.isTrue(Arrays.equals(decodedGeometryColumn.indexBuffer().get().array(), expectedIndexBuffer)); + Assert.isTrue(decodedGeometryColumn.indexBuffer().isPresent()); + Assert.isTrue(decodedGeometryColumn.numTrianglesPerPolygonBuffer().isPresent()); + Assert.equals( + decodedGeometryColumn.numTrianglesPerPolygonBuffer().get().array()[0], + expectedTriangulatedPolygon.getNumTrianglesPerPolygon()); + Assert.isTrue( + Arrays.equals(decodedGeometryColumn.indexBuffer().get().array(), expectedIndexBuffer)); + } + + @Test + public void testTriangulateGeometryColumnForMultiPolygonLayer() throws IOException { + var decodedMvTile = MvtUtils.decodeMvt(mvtFilePath); + + var geometries = new ArrayList(); + var featureIds = new ArrayList(); + var polygonFeature = decodedMvTile.layers().get(0).features().get(0); + geometries.add(polygonFeature.geometry()); + featureIds.add(polygonFeature.id()); + var sortSettings = new GeometryEncoder.SortSettings(false, featureIds); + + var encodedGeometryColumn = + GeometryEncoder.encodeGeometryColumn(geometries, physicalLevelTechnique, sortSettings, true); + var decodedGeometryColumn = + VectorizedGeometryDecoder.decodeGeometryColumn(encodedGeometryColumn.encodedValues(), encodedGeometryColumn.numStreams(), new IntWrapper(0)); + + var expectedTriangulatedMultiPolygon = + TriangulationUtils.triangulatePolygonWithHoles((MultiPolygon) polygonFeature.geometry()); + var indexBuffer = expectedTriangulatedMultiPolygon.getIndexBuffer(); + + var expectedIndexBuffer = new int[indexBuffer.size()]; + for (int i = 0; i < indexBuffer.size(); i++) { + expectedIndexBuffer[i] = indexBuffer.get(i); } - @Test - public void testTriangulateGeometryColumnForMultiPolygonLayer() throws IOException { - var decodedMvTile = MvtUtils.decodeMvt(mvtFilePath); - - var geometries = new ArrayList(); - var featureIds = new ArrayList(); - var polygonFeature = decodedMvTile.layers().get(0).features().get(0); - geometries.add(polygonFeature.geometry()); - featureIds.add(polygonFeature.id()); - - var encodedGeometryColumn = GeometryEncoder.encodeGeometryColumn(geometries, physicalLevelTechnique, featureIds, true); - var decodedGeometryColumn = VectorizedGeometryDecoder.decodeGeometryColumn(encodedGeometryColumn.getMiddle(), encodedGeometryColumn.getLeft(), new IntWrapper(0)); - - var expectedTriangulatedMultiPolygon = TriangulationUtils.triangulatePolygonWithHoles((MultiPolygon) polygonFeature.geometry()); - var indexBuffer = expectedTriangulatedMultiPolygon.getIndexBuffer(); - - var expectedIndexBuffer = new int[indexBuffer.size()]; - for (int i = 0; i < indexBuffer.size(); i++) { - expectedIndexBuffer[i] = indexBuffer.get(i); - } - - Assert.isTrue(decodedGeometryColumn.indexBuffer().isPresent()); - Assert.isTrue(decodedGeometryColumn.numTrianglesPerPolygonBuffer().isPresent()); - Assert.equals(decodedGeometryColumn.numTrianglesPerPolygonBuffer().get().array()[0], expectedTriangulatedMultiPolygon.getNumTrianglesPerPolygon()); - Assert.isTrue(Arrays.equals(decodedGeometryColumn.indexBuffer().get().array(), expectedIndexBuffer)); - } + Assert.isTrue(decodedGeometryColumn.indexBuffer().isPresent()); + Assert.isTrue(decodedGeometryColumn.numTrianglesPerPolygonBuffer().isPresent()); + Assert.equals( + decodedGeometryColumn.numTrianglesPerPolygonBuffer().get().array()[0], + expectedTriangulatedMultiPolygon.getNumTrianglesPerPolygon()); + Assert.isTrue( + Arrays.equals(decodedGeometryColumn.indexBuffer().get().array(), expectedIndexBuffer)); + } } diff --git a/java/src/test/java/com/mlt/decoder/MltDecoderTest.java b/java/src/test/java/com/mlt/decoder/MltDecoderTest.java index 6b4c0e31..8a686787 100644 --- a/java/src/test/java/com/mlt/decoder/MltDecoderTest.java +++ b/java/src/test/java/com/mlt/decoder/MltDecoderTest.java @@ -199,10 +199,10 @@ private DecodingResult testTile( var includeIds = true; var mlTile = MltConverter.convertMvt( - mvTile, new ConversionConfig(includeIds, false, optimizations), tileMetadata); + mvTile, new ConversionConfig(includeIds, false, optimizations), tileMetadata, false); var mlTileAdvanced = MltConverter.convertMvt( - mvTile, new ConversionConfig(includeIds, true, optimizations), tileMetadata); + mvTile, new ConversionConfig(includeIds, true, optimizations), tileMetadata, false); int numErrors = -1; int numErrorsAdvanced = -1; if (decoder == DecoderType.SEQUENTIAL || decoder == DecoderType.BOTH) { From 68c984deb395faeaeba1ae90effb8b3bc913c525 Mon Sep 17 00:00:00 2001 From: Felix Date: Wed, 13 Nov 2024 11:07:44 +0100 Subject: [PATCH 8/8] Format code --- .../converter/encodings/GeometryEncoder.java | 46 +++++++++++++------ .../vectorized/VectorizedGeometryDecoder.java | 4 +- .../encodings/GeometryEncodingTest.java | 27 +++++++---- 3 files changed, 55 insertions(+), 22 deletions(-) diff --git a/java/src/main/java/com/mlt/converter/encodings/GeometryEncoder.java b/java/src/main/java/com/mlt/converter/encodings/GeometryEncoder.java index d499ae31..41d9b32f 100644 --- a/java/src/main/java/com/mlt/converter/encodings/GeometryEncoder.java +++ b/java/src/main/java/com/mlt/converter/encodings/GeometryEncoder.java @@ -280,12 +280,16 @@ public static EncodedGeometryColumn encodeGeometryColumn( Arrays.stream(zigZagDeltaVertexBuffer).boxed().collect(Collectors.toList()), physicalLevelTechnique); - var encodedGeometryColumn = new EncodedGeometryColumn( + var encodedGeometryColumn = + new EncodedGeometryColumn( numStreams + 1, ArrayUtils.addAll(encodedTopologyStreams, encodedVertexBufferStream), maxVertexValue, geometryColumnSorted); - return triangulatePolygons ? buildTriangulatedEncodedGeometryColumn(encodedGeometryColumn, encodedIndexBuffer, encodedNumTrianglesBuffer) : encodedGeometryColumn; + return triangulatePolygons + ? buildTriangulatedEncodedGeometryColumn( + encodedGeometryColumn, encodedIndexBuffer, encodedNumTrianglesBuffer) + : encodedGeometryColumn; } else if (dictionaryEncodedSize < plainVertexBufferSize && dictionaryEncodedSize <= mortonDictionaryEncodedSize) { var encodedVertexOffsetStream = @@ -299,12 +303,18 @@ public static EncodedGeometryColumn encodeGeometryColumn( encodeVertexBuffer( Arrays.stream(zigZagDeltaVertexDictionary).boxed().collect(Collectors.toList()), physicalLevelTechnique); - var encodedGeometryColumn = new EncodedGeometryColumn( - numStreams + 2, CollectionUtils.concatByteArrays(encodedTopologyStreams, encodedVertexOffsetStream, encodedVertexDictionaryStream), - maxVertexValue, - false); + var encodedGeometryColumn = + new EncodedGeometryColumn( + numStreams + 2, + CollectionUtils.concatByteArrays( + encodedTopologyStreams, encodedVertexOffsetStream, encodedVertexDictionaryStream), + maxVertexValue, + false); - return triangulatePolygons ? buildTriangulatedEncodedGeometryColumn(encodedGeometryColumn, encodedIndexBuffer, encodedNumTrianglesBuffer) : encodedGeometryColumn; + return triangulatePolygons + ? buildTriangulatedEncodedGeometryColumn( + encodedGeometryColumn, encodedIndexBuffer, encodedNumTrianglesBuffer) + : encodedGeometryColumn; } else { var encodedMortonVertexOffsetStream = IntegerEncoder.encodeIntStream( @@ -321,15 +331,20 @@ public static EncodedGeometryColumn encodeGeometryColumn( zOrderCurve.coordinateShift(), physicalLevelTechnique); - var encodedGeometryColumn = new EncodedGeometryColumn( + var encodedGeometryColumn = + new EncodedGeometryColumn( numStreams + 2, - CollectionUtils.concatByteArrays(encodedTopologyStreams, - encodedMortonVertexOffsetStream, - encodedMortonEncodedVertexDictionaryStream), + CollectionUtils.concatByteArrays( + encodedTopologyStreams, + encodedMortonVertexOffsetStream, + encodedMortonEncodedVertexDictionaryStream), maxVertexValue, geometryColumnSorted); - return triangulatePolygons ? buildTriangulatedEncodedGeometryColumn(encodedGeometryColumn, encodedIndexBuffer, encodedNumTrianglesBuffer) : encodedGeometryColumn; + return triangulatePolygons + ? buildTriangulatedEncodedGeometryColumn( + encodedGeometryColumn, encodedIndexBuffer, encodedNumTrianglesBuffer) + : encodedGeometryColumn; } } @@ -461,6 +476,11 @@ private static EncodedGeometryColumn buildTriangulatedEncodedGeometryColumn( EncodedGeometryColumn encodedGeometryColumn, byte[] encodedIndexBuffer, byte[] encodedNumTrianglesPerPolygon) { - return new EncodedGeometryColumn(encodedGeometryColumn.numStreams + 2, CollectionUtils.concatByteArrays(encodedGeometryColumn.encodedValues, encodedIndexBuffer, encodedNumTrianglesPerPolygon), encodedGeometryColumn.maxVertexValue, encodedGeometryColumn.geometryColumnSorted); + return new EncodedGeometryColumn( + encodedGeometryColumn.numStreams + 2, + CollectionUtils.concatByteArrays( + encodedGeometryColumn.encodedValues, encodedIndexBuffer, encodedNumTrianglesPerPolygon), + encodedGeometryColumn.maxVertexValue, + encodedGeometryColumn.geometryColumnSorted); } } diff --git a/java/src/main/java/com/mlt/decoder/vectorized/VectorizedGeometryDecoder.java b/java/src/main/java/com/mlt/decoder/vectorized/VectorizedGeometryDecoder.java index 5d6db55f..ca7dd5d1 100644 --- a/java/src/main/java/com/mlt/decoder/vectorized/VectorizedGeometryDecoder.java +++ b/java/src/main/java/com/mlt/decoder/vectorized/VectorizedGeometryDecoder.java @@ -78,7 +78,9 @@ public static VectorizedGeometryDecoder.GeometryColumn decodeGeometryColumn( } break; default: - vertexOffsets = VectorizedIntegerDecoder.decodeIntStream(tile, offset, geometryStreamMetadata, false); + vertexOffsets = + VectorizedIntegerDecoder.decodeIntStream( + tile, offset, geometryStreamMetadata, false); } break; case DATA: diff --git a/java/src/test/java/com/mlt/converter/encodings/GeometryEncodingTest.java b/java/src/test/java/com/mlt/converter/encodings/GeometryEncodingTest.java index 87d21023..2b01a023 100644 --- a/java/src/test/java/com/mlt/converter/encodings/GeometryEncodingTest.java +++ b/java/src/test/java/com/mlt/converter/encodings/GeometryEncodingTest.java @@ -18,8 +18,7 @@ import org.locationtech.jts.util.Assert; public class GeometryEncodingTest { - Path mvtFilePath = - Paths.get(TestSettings.BING_MVT_PATH, "4-8-5" + ".mvt"); + Path mvtFilePath = Paths.get(TestSettings.BING_MVT_PATH, "4-8-5" + ".mvt"); PhysicalLevelTechnique physicalLevelTechnique = PhysicalLevelTechnique.FAST_PFOR; @@ -45,9 +44,13 @@ public void testTriangulatedGeometryEncodingForTile() throws IOException { var sortSettings = new GeometryEncoder.SortSettings(false, featureIds); var encodedGeometryColumn = - GeometryEncoder.encodeGeometryColumn(geometries, physicalLevelTechnique, sortSettings, true); + GeometryEncoder.encodeGeometryColumn( + geometries, physicalLevelTechnique, sortSettings, true); var decodedGeometryColumn = - VectorizedGeometryDecoder.decodeGeometryColumn(encodedGeometryColumn.encodedValues(), encodedGeometryColumn.numStreams(), new IntWrapper(0)); + VectorizedGeometryDecoder.decodeGeometryColumn( + encodedGeometryColumn.encodedValues(), + encodedGeometryColumn.numStreams(), + new IntWrapper(0)); Assert.isTrue(decodedGeometryColumn.indexBuffer().isPresent()); Assert.isTrue(decodedGeometryColumn.numTrianglesPerPolygonBuffer().isPresent()); @@ -65,9 +68,13 @@ public void testTriangulateGeometryColumnForPolygonLayer() throws IOException { var sortSettings = new GeometryEncoder.SortSettings(false, featureIds); var encodedGeometryColumn = - GeometryEncoder.encodeGeometryColumn(geometries, physicalLevelTechnique, sortSettings, true); + GeometryEncoder.encodeGeometryColumn( + geometries, physicalLevelTechnique, sortSettings, true); var decodedGeometryColumn = - VectorizedGeometryDecoder.decodeGeometryColumn(encodedGeometryColumn.encodedValues(), encodedGeometryColumn.numStreams(), new IntWrapper(0)); + VectorizedGeometryDecoder.decodeGeometryColumn( + encodedGeometryColumn.encodedValues(), + encodedGeometryColumn.numStreams(), + new IntWrapper(0)); var expectedTriangulatedPolygon = TriangulationUtils.triangulatePolygon((Polygon) polygonFeature.geometry()); @@ -99,9 +106,13 @@ public void testTriangulateGeometryColumnForMultiPolygonLayer() throws IOExcepti var sortSettings = new GeometryEncoder.SortSettings(false, featureIds); var encodedGeometryColumn = - GeometryEncoder.encodeGeometryColumn(geometries, physicalLevelTechnique, sortSettings, true); + GeometryEncoder.encodeGeometryColumn( + geometries, physicalLevelTechnique, sortSettings, true); var decodedGeometryColumn = - VectorizedGeometryDecoder.decodeGeometryColumn(encodedGeometryColumn.encodedValues(), encodedGeometryColumn.numStreams(), new IntWrapper(0)); + VectorizedGeometryDecoder.decodeGeometryColumn( + encodedGeometryColumn.encodedValues(), + encodedGeometryColumn.numStreams(), + new IntWrapper(0)); var expectedTriangulatedMultiPolygon = TriangulationUtils.triangulatePolygonWithHoles((MultiPolygon) polygonFeature.geometry());