From 992d90e04c9b2c57801869748d2aac0751060990 Mon Sep 17 00:00:00 2001 From: Xun Li Date: Tue, 14 Nov 2023 18:56:15 -0800 Subject: [PATCH] feat(GeoArrow): getBinaryGeometriesFromArrow enhancement (#2785) --- modules/arrow/package.json | 1 + .../convert-geoarrow-to-binary-geometry.ts | 229 ++++++++++++++++-- modules/arrow/src/index.ts | 9 +- modules/arrow/test/data/line.arrow | Bin 3146 -> 3202 bytes modules/arrow/test/data/multiline.arrow | Bin 3370 -> 3466 bytes modules/arrow/test/data/multipoint.arrow | Bin 3146 -> 3202 bytes modules/arrow/test/data/point.arrow | Bin 2954 -> 2986 bytes modules/arrow/test/data/polygon.arrow | Bin 3306 -> 3434 bytes ...onvert-geoarrow-to-binary-geometry.spec.ts | 140 +++++++---- .../convert-geoarrow-to-geojson.spec.ts | 101 +++++++- yarn.lock | 2 +- 11 files changed, 399 insertions(+), 83 deletions(-) diff --git a/modules/arrow/package.json b/modules/arrow/package.json index bbadc01aaf..a371b17b27 100644 --- a/modules/arrow/package.json +++ b/modules/arrow/package.json @@ -49,6 +49,7 @@ "@loaders.gl/gis": "4.0.3", "@loaders.gl/loader-utils": "4.0.3", "@loaders.gl/schema": "4.0.3", + "@math.gl/polygon": "4.0.0", "apache-arrow": "^13.0.0" }, "gitHead": "c95a4ff72512668a93d9041ce8636bac09333fd5" diff --git a/modules/arrow/src/geoarrow/convert-geoarrow-to-binary-geometry.ts b/modules/arrow/src/geoarrow/convert-geoarrow-to-binary-geometry.ts index b06a437583..9692965614 100644 --- a/modules/arrow/src/geoarrow/convert-geoarrow-to-binary-geometry.ts +++ b/modules/arrow/src/geoarrow/convert-geoarrow-to-binary-geometry.ts @@ -2,28 +2,49 @@ // Copyright (c) vis.gl contributors import * as arrow from 'apache-arrow'; +import {earcut} from '@math.gl/polygon'; import {BinaryFeatureCollection as BinaryFeatures} from '@loaders.gl/schema'; import {GeoArrowEncoding} from '@loaders.gl/gis'; import {updateBoundsFromGeoArrowSamples} from './get-arrow-bounds'; +import {TypedArray} from '@loaders.gl/loader-utils'; /** * Binary data from geoarrow column and can be used by e.g. deck.gl GeojsonLayer */ export type BinaryDataFromGeoArrow = { + /** Binary format geometries, an array of BinaryFeatureCollection */ binaryGeometries: BinaryFeatures[]; + /** Boundary of the binary geometries */ bounds: [number, number, number, number]; + /** Feature types of the binary geometries */ featureTypes: {polygon: boolean; point: boolean; line: boolean}; + /** (Optional) mean centers of the binary geometries for e.g. polygon filtering */ + meanCenters?: number[][]; }; +/** + * Binary geometry content returned from getBinaryGeometriesFromChunk + */ type BinaryGeometryContent = { + // Array of Point feature indexes by vertex featureIds: Uint32Array; + /** Flat coordinate array of e.g. x, y or x,y,z */ flatCoordinateArray: Float64Array; + /** Dimention of each position */ nDim: number; + /** Array of geometry offsets: the start index of primitive geometry */ geomOffset: Int32Array; + /** Array of geometry indicies: the start index of each geometry */ geometryIndicies: Uint16Array; + /** (Optional) indices of triangels returned from polygon tessellation (Polygon only) */ + triangles?: Uint32Array; + /** (Optional) array of mean center of each geometry */ + meanCenters?: Float64Array; }; -// binary geometry template, see deck.gl BinaryGeometry +/** + * binary geometry template, see deck.gl BinaryGeometry + */ export const BINARY_GEOMETRY_TEMPLATE = { globalFeatureIds: {value: new Uint32Array(0), size: 1}, positions: {value: new Float32Array(0), size: 2}, @@ -32,16 +53,25 @@ export const BINARY_GEOMETRY_TEMPLATE = { featureIds: {value: new Uint32Array(0), size: 1} }; +export type BinaryGeometriesFromArrowOptions = { + /** option to specify which chunk to get binary geometries from, for progressive rendering */ + chunkIndex?: number; + /** option to get mean centers from geometries, for polygon filtering */ + meanCenter?: boolean; +}; + /** * get binary geometries from geoarrow column * * @param geoColumn the geoarrow column, e.g. arrowTable.getChildAt(geoColumnIndex) * @param geoEncoding the geo encoding of the geoarrow column, e.g. getGeoArrowEncoding(arrowTable.schema, geoColumnName) + * @param options options for getting binary geometries {meanCenter: boolean} * @returns BinaryDataFromGeoArrow */ export function getBinaryGeometriesFromArrow( geoColumn: arrow.Vector, - geoEncoding: GeoArrowEncoding + geoEncoding: GeoArrowEncoding, + options?: BinaryGeometriesFromArrowOptions ): BinaryDataFromGeoArrow { const featureTypes = { polygon: geoEncoding === 'geoarrow.multipolygon' || geoEncoding === 'geoarrow.polygon', @@ -49,16 +79,14 @@ export function getBinaryGeometriesFromArrow( line: geoEncoding === 'geoarrow.multilinestring' || geoEncoding === 'geoarrow.linestring' }; - const chunks = geoColumn.data; + const chunks = options?.chunkIndex ? [geoColumn.data[options?.chunkIndex]] : geoColumn.data; let bounds: [number, number, number, number] = [Infinity, Infinity, -Infinity, -Infinity]; let globalFeatureIdOffset = 0; const binaryGeometries: BinaryFeatures[] = []; chunks.forEach((chunk) => { - const {featureIds, flatCoordinateArray, nDim, geomOffset} = getBinaryGeometriesFromChunk( - chunk, - geoEncoding - ); + const {featureIds, flatCoordinateArray, nDim, geomOffset, triangles} = + getBinaryGeometriesFromChunk(chunk, geoEncoding); const globalFeatureIds = new Uint32Array(featureIds.length); for (let i = 0; i < featureIds.length; i++) { @@ -79,7 +107,6 @@ export function getBinaryGeometriesFromArrow( // TODO: check if chunks are sequentially accessed globalFeatureIdOffset += chunk.length; - // NOTE: deck.gl defines the BinaryFeatures structure must have points, lines, polygons even if they are empty binaryGeometries.push({ shape: 'binary-feature-collection', @@ -99,22 +126,111 @@ export function getBinaryGeometriesFromArrow( ...BINARY_GEOMETRY_TEMPLATE, ...(featureTypes.polygon ? binaryContent : {}), polygonIndices: { - // NOTE: polygonIndices and primitivePolygonIndices are not used together to render polygon (binary) with holes - // for GeoJsonLayer with binary geometries in deck.gl currently, so we pass geomOffset and triangles. + // use geomOffset as polygonIndices same as primitivePolygonIndices since we are using earcut to get triangule indices value: featureTypes.polygon ? geomOffset : new Uint16Array(0), size: 1 }, primitivePolygonIndices: { value: featureTypes.polygon ? geomOffset : new Uint16Array(0), size: 1 - } + }, + ...(triangles ? {triangles: {value: triangles, size: 1}} : {}) } }); bounds = updateBoundsFromGeoArrowSamples(flatCoordinateArray, nDim, bounds); }); - return {binaryGeometries, bounds, featureTypes}; + return { + binaryGeometries, + bounds, + featureTypes, + ...(options?.meanCenter + ? {meanCenters: getMeanCentersFromBinaryGeometries(binaryGeometries)} + : {}) + }; +} + +/** + * Get mean centers from binary geometries + * @param binaryGeometries binary geometries from geoarrow column, an array of BinaryFeatureCollection + * @returns mean centers of the binary geometries + */ +export function getMeanCentersFromBinaryGeometries(binaryGeometries: BinaryFeatures[]): number[][] { + const globalMeanCenters: number[][] = []; + binaryGeometries.forEach((binaryGeometry: BinaryFeatures) => { + let binaryGeometryType: string | null = null; + if (binaryGeometry.points && binaryGeometry.points.positions.value.length > 0) { + binaryGeometryType = 'points'; + } else if (binaryGeometry.lines && binaryGeometry.lines.positions.value.length > 0) { + binaryGeometryType = 'lines'; + } else if (binaryGeometry.polygons && binaryGeometry.polygons.positions.value.length > 0) { + binaryGeometryType = 'polygons'; + } + + const binaryContent = binaryGeometryType ? binaryGeometry[binaryGeometryType] : null; + if (binaryContent && binaryGeometryType !== null) { + const featureIds = binaryContent.featureIds.value; + const flatCoordinateArray = binaryContent.positions.value; + const nDim = binaryContent.positions.size; + const primitivePolygonIndices = binaryContent.primitivePolygonIndices?.value; + + const meanCenters = getMeanCentersFromGeometry( + featureIds, + flatCoordinateArray, + nDim, + binaryGeometryType, + primitivePolygonIndices + ); + meanCenters.forEach((center) => { + globalMeanCenters.push(center); + }); + } + }); + return globalMeanCenters; +} + +/** + * Get mean centers from raw coordinates and feature ids + * @param featureIds Array of feature ids indexes by vertex + * @param flatCoordinateArray Array of vertex, e.g. x, y or x, y, z, positions + * @param nDim number of dimensions per position + * @returns - mean centers of each polygon + */ +function getMeanCentersFromGeometry( + featureIds: TypedArray, + flatCoordinateArray: TypedArray, + nDim: number, + geometryType: string, + primitivePolygonIndices?: TypedArray +) { + const meanCenters: number[][] = []; + const vertexCount = flatCoordinateArray.length; + let vertexIndex = 0; + while (vertexIndex < vertexCount) { + const featureId = featureIds[vertexIndex / nDim]; + const center = [0, 0]; + let vertexCountInFeature = 0; + while (vertexIndex < vertexCount && featureIds[vertexIndex / nDim] === featureId) { + if ( + geometryType === 'polygons' && + primitivePolygonIndices && + primitivePolygonIndices.indexOf(vertexIndex / nDim) >= 0 + ) { + // skip the first point since it is the same as the last point in each ring for polygons + vertexIndex += nDim; + } else { + center[0] += flatCoordinateArray[vertexIndex]; + center[1] += flatCoordinateArray[vertexIndex + 1]; + vertexIndex += nDim; + vertexCountInFeature++; + } + } + center[0] /= vertexCountInFeature; + center[1] /= vertexCountInFeature; + meanCenters.push(center); + } + return meanCenters; } /** @@ -142,6 +258,53 @@ function getBinaryGeometriesFromChunk( } } +/** + * get triangle indices. Allows deck.gl to skip performing costly triangulation on main thread. + * @param polygonIndices Indices within positions of the start of each simple Polygon + * @param primitivePolygonIndices Indices within positions of the start of each primitive Polygon/ring + * @param flatCoordinateArray Array of x, y or x, y, z positions + * @param nDim - number of dimensions per position + * @returns + */ +export function getTriangleIndices( + polygonIndices: Uint16Array, + primitivePolygonIndices: Int32Array, + flatCoordinateArray: Float64Array, + nDim: number +): Uint32Array { + let primitiveIndex = 0; + const triangles: number[] = []; + // loop polygonIndices to get triangles + for (let i = 0; i < polygonIndices.length - 1; i++) { + const startIdx = polygonIndices[i]; + const endIdx = polygonIndices[i + 1]; + // get subarray of flatCoordinateArray + const slicedFlatCoords = flatCoordinateArray.subarray(startIdx * nDim, endIdx * nDim); + // get holeIndices for earcut + const holeIndices: number[] = []; + while (primitivePolygonIndices[primitiveIndex] < endIdx) { + if (primitivePolygonIndices[primitiveIndex] > startIdx) { + holeIndices.push(primitivePolygonIndices[primitiveIndex] - startIdx); + } + primitiveIndex++; + } + const triangleIndices = earcut( + slicedFlatCoords, + holeIndices.length > 0 ? holeIndices : undefined, + nDim + ); + for (let j = 0; j < triangleIndices.length; j++) { + triangles.push(triangleIndices[j] + startIdx); + } + } + // convert traingles to Uint32Array + const trianglesUint32 = new Uint32Array(triangles.length); + for (let i = 0; i < triangles.length; i++) { + trianglesUint32[i] = triangles[i]; + } + return trianglesUint32; +} + /** * get binary polygons from geoarrow polygon column * @param chunk one chunk of geoarrow polygon column @@ -178,12 +341,14 @@ function getBinaryPolygonsFromChunk(chunk: arrow.Data, geoEncoding: string): Bin } } + const triangles = getTriangleIndices(geometryIndicies, geomOffset, flatCoordinateArray, nDim); return { featureIds, flatCoordinateArray, nDim, geomOffset, - geometryIndicies + geometryIndicies, + triangles }; } @@ -209,11 +374,23 @@ function getBinaryLinesFromChunk(chunk: arrow.Data, geoEncoding: string): Binary const numOfVertices = flatCoordinateArray.length / nDim; const featureIds = new Uint32Array(numOfVertices); - for (let i = 0; i < chunk.length; i++) { - const startIdx = geomOffset[i]; - const endIdx = geomOffset[i + 1]; - for (let j = startIdx; j < endIdx; j++) { - featureIds[j] = i; + + if (isMultiLineString) { + const partData = chunk.valueOffsets; + for (let i = 0; i < partData.length - 1; i++) { + const startIdx = geomOffset[partData[i]]; + const endIdx = geomOffset[partData[i + 1]]; + for (let j = startIdx; j < endIdx; j++) { + featureIds[j] = i; + } + } + } else { + for (let i = 0; i < chunk.length; i++) { + const startIdx = geomOffset[i]; + const endIdx = geomOffset[i + 1]; + for (let j = startIdx; j < endIdx; j++) { + featureIds[j] = i; + } } } @@ -248,8 +425,20 @@ function getBinaryPointsFromChunk(chunk: arrow.Data, geoEncoding: string): Binar const numOfVertices = flatCoordinateArray.length / nDim; const featureIds = new Uint32Array(numOfVertices); - for (let i = 0; i < chunk.length; i++) { - featureIds[i] = i; + + if (isMultiPoint) { + const partData = chunk.valueOffsets; + for (let i = 0; i < partData.length - 1; i++) { + const startIdx = partData[i]; + const endIdx = partData[i + 1]; + for (let j = startIdx; j < endIdx; j++) { + featureIds[j] = i; + } + } + } else { + for (let i = 0; i < chunk.length; i++) { + featureIds[i] = i; + } } return { diff --git a/modules/arrow/src/index.ts b/modules/arrow/src/index.ts index 0378cbfdca..32d65b1271 100644 --- a/modules/arrow/src/index.ts +++ b/modules/arrow/src/index.ts @@ -64,10 +64,15 @@ export type {GeoArrowEncoding} from '@loaders.gl/gis'; // getGeometryColumnsFromArrowTable, // getGeoArrowEncoding -export type {BinaryDataFromGeoArrow} from './geoarrow/convert-geoarrow-to-binary-geometry'; +export type { + BinaryDataFromGeoArrow, + BinaryGeometriesFromArrowOptions +} from './geoarrow/convert-geoarrow-to-binary-geometry'; export { BINARY_GEOMETRY_TEMPLATE, - getBinaryGeometriesFromArrow + getBinaryGeometriesFromArrow, + getTriangleIndices, + getMeanCentersFromBinaryGeometries } from './geoarrow/convert-geoarrow-to-binary-geometry'; export {parseGeometryFromArrow} from './geoarrow/convert-geoarrow-to-geojson'; diff --git a/modules/arrow/test/data/line.arrow b/modules/arrow/test/data/line.arrow index 2db7920191fba9c86d6d3b9d9428184ba51b2c2a..2eac39b3db5035417b243e6f891c21cd3fc8b8ab 100644 GIT binary patch delta 278 zcmX>l(ImMcfQ7MQav+O5Bh%zU7WK&+SOg}oU=hgWVSoS$D9r_>HJ~(%g7IPMEnpH* z8W-OINgeCtiLAntBUlB1SOMq)4ojFMBap!a<*)*2m@D!Ub5jjLgb_?7L>fXX?b(i~7ehr{L_tN~1{#(D-i#*=wDv>7WlTXG1p007G!95(;} delta 245 zcmZpYJSDLqfQ8Xvav+O5Bje;k7Ij9J$%(AulQ*z%OkTjkQ7^y%0jyA30!qUu7@q?w zpa7E~z(-aGGMf=iAC%7p7iOHS$tpiNf>l5iSt&?sUSe*l;bcRgqbIO%@GvlZu!m^+ cV88hTYXB3gp`L+`;bd11ZAOR9g&cw`0BN2Vx&QzG diff --git a/modules/arrow/test/data/multiline.arrow b/modules/arrow/test/data/multiline.arrow index 2d5728a4653fc05e0f2880a3b8149fc0c1ec1c9e..b4404be8996e2a5c8f482c9ec9025b9af3590aff 100644 GIT binary patch delta 334 zcmZ1_)g`^5gN5S56 z-U228rD1##C_eyA9tU5Dp#e!D8<5R3c_FL(09Q0m@g{?9Ud)#A=~u Spkpz46^9<?iiE5Ib6 zG>p#!MUt|@Ye1%nDato^r(3uL8C$RF!f}E9?n44+@Rn3G% gvrN`xbKksyEtHAXM9)CSWHJ}09%I2~OU^(R0K*~~w*UYD diff --git a/modules/arrow/test/data/multipoint.arrow b/modules/arrow/test/data/multipoint.arrow index 7123a434320b2ec0acda42e5a3d08513071040c1..ff545eecdbd490e0e836b81b15ac4e334e6eee96 100644 GIT binary patch delta 287 zcmX>l(ImMcfQ7MQav+O5Bh%zU7WK&+SOg}oU=gV2VSoS$D9r_>HJ~(%g7IPMEnpH* z8W-OINgXSY&4k1UGAD<#3Qmq-RRFq&!xkpRh@=jr2j-@{#N1Rv5McyU2{Dfa#GO2q nRaqV)&f$Pc3qbh-o42zDGO?QI8R(cy=H<|4tk`VHA;mI$1uKsrNK0N~ZmJPfo@w%0R^`nfSOb_? Vjr0t3j3&EsXfrx&F60no0RTgD6@>r* diff --git a/modules/arrow/test/data/point.arrow b/modules/arrow/test/data/point.arrow index 0b1835023b23a8a0a838b73c2faf5c9ed3fe1f42..3a027d358b570da57e12dbfee40a4ae37fd346b4 100644 GIT binary patch delta 228 zcmeAYUnRalfQ8XxvLK5*BhzF<7WK&mECQ1=SOfxj7$86bN^?PJ4JZwxaPbvj3V<{V z5Hn4F$)X(t5(M#(0V9yZ1QlQf(lDFy5_3}xL4*;q>JRo19*kyifXX^-HemHIE1efE7whKxr6-ix1NWQpW7}qHgki7Eu|H-Fb<*sYY-!KG;K;AM7^=u=+Ex8tNJ77*1ZouEVIY`6ByG FW&n>16EOe) diff --git a/modules/arrow/test/data/polygon.arrow b/modules/arrow/test/data/polygon.arrow index 01a963978ace48e914cc3dc309cfc2cb7f2922a5..f62c688d430009a5387106913a0237a4d0244052 100644 GIT binary patch delta 459 zcmaDQ`ATX-0Sn`c$%QQPj7*a!vZznKz#=gD04oQO<`CpzfB*?7%{5t*RlXi3Y5^66 zQ7}GC-UB89rE&2WAgN;mvYC+hASN2W9$f%~&xK}y2pXRe$xv1x8>T)lF*nr^L>M77 zAgcw5vNA9OHB4T}B0hNo3&$ijvB`XF96-!Dc?B@wHn6aJsW?D%qtj|oaa=UIdKHJs t25cUzd_eD4Z|39t$T&HVgP+&XP|rXIM8wwC)=uu?P-A?tc_W7<3jn>cDoOwV delta 322 zcmaDQ^-6L>0SjZsChugCpL~FY!&86(0$8E61eAtRFg^!VKmjI! z#^;#)5@_NN7J)zqBo%BxHX{-r#3Yr^GWj5jxGu~PWZfV)=OyN*8bT#uax9Yt*(4|H zu?a{qI6$RfG?dH0FnI%;2P+Q~14H%ZKF*JflizXh^BUU59wi>f diff --git a/modules/arrow/test/geoarrow/convert-geoarrow-to-binary-geometry.spec.ts b/modules/arrow/test/geoarrow/convert-geoarrow-to-binary-geometry.spec.ts index e642710f2e..96a8338228 100644 --- a/modules/arrow/test/geoarrow/convert-geoarrow-to-binary-geometry.spec.ts +++ b/modules/arrow/test/geoarrow/convert-geoarrow-to-binary-geometry.spec.ts @@ -1,6 +1,5 @@ import test, {Test} from 'tape-promise/tape'; -import {Table as ApacheArrowTable} from 'apache-arrow'; import {getGeometryColumnsFromSchema} from '@loaders.gl/gis'; import {fetchFile, parse} from '@loaders.gl/core'; import { @@ -27,10 +26,10 @@ const expectedPointBinaryGeometry = { points: { ...BINARY_GEOMETRY_TEMPLATE, type: 'Point', - globalFeatureIds: {value: new Uint32Array([0]), size: 1}, - positions: {value: new Float64Array([1, 1]), size: 2}, - properties: [{index: 0}], - featureIds: {value: new Uint32Array([0]), size: 1} + globalFeatureIds: {value: new Uint32Array([0, 1]), size: 1}, + positions: {value: new Float64Array([1, 1, 2, 2]), size: 2}, + properties: [{index: 0}, {index: 1}], + featureIds: {value: new Uint32Array([0, 1]), size: 1} }, lines: { ...BINARY_GEOMETRY_TEMPLATE, @@ -45,8 +44,12 @@ const expectedPointBinaryGeometry = { } } ], - bounds: [1, 1, 1, 1], - featureTypes: {polygon: false, point: true, line: false} + bounds: [1, 1, 2, 2], + featureTypes: {polygon: false, point: true, line: false}, + meanCenters: [ + [1, 1], + [2, 2] + ] }; const expectedMultiPointBinaryGeometry = { @@ -56,10 +59,10 @@ const expectedMultiPointBinaryGeometry = { points: { ...BINARY_GEOMETRY_TEMPLATE, type: 'Point', - globalFeatureIds: {value: new Uint32Array([0, 0]), size: 1}, - positions: {value: new Float64Array([1, 1, 2, 2]), size: 2}, - properties: [{index: 0}], - featureIds: {value: new Uint32Array([0, 0]), size: 1} + globalFeatureIds: {value: new Uint32Array([0, 0, 1, 1]), size: 1}, + positions: {value: new Float64Array([1, 1, 2, 2, 3, 3, 4, 4]), size: 2}, + properties: [{index: 0}, {index: 1}], + featureIds: {value: new Uint32Array([0, 0, 1, 1]), size: 1} }, lines: { ...BINARY_GEOMETRY_TEMPLATE, @@ -74,8 +77,12 @@ const expectedMultiPointBinaryGeometry = { } } ], - bounds: [1, 1, 2, 2], - featureTypes: {polygon: false, point: true, line: false} + bounds: [1, 1, 4, 4], + featureTypes: {polygon: false, point: true, line: false}, + meanCenters: [ + [1.5, 1.5], + [3.5, 3.5] + ] }; const expectedLineBinaryGeometry = { @@ -89,11 +96,11 @@ const expectedLineBinaryGeometry = { lines: { ...BINARY_GEOMETRY_TEMPLATE, type: 'LineString', - globalFeatureIds: {value: new Uint32Array([0, 0]), size: 1}, - positions: {value: new Float64Array([0, 0, 1, 1]), size: 2}, - properties: [{index: 0}], - featureIds: {value: new Uint32Array([0, 0]), size: 1}, - pathIndices: {value: new Int32Array([0, 2]), size: 1} + globalFeatureIds: {value: new Uint32Array([0, 0, 1, 1]), size: 1}, + positions: {value: new Float64Array([0, 0, 1, 1, 2, 2, 3, 3]), size: 2}, + properties: [{index: 0}, {index: 1}], + featureIds: {value: new Uint32Array([0, 0, 1, 1]), size: 1}, + pathIndices: {value: new Int32Array([0, 2, 4]), size: 1} }, polygons: { ...BINARY_GEOMETRY_TEMPLATE, @@ -103,8 +110,12 @@ const expectedLineBinaryGeometry = { } } ], - bounds: [0, 0, 1, 1], - featureTypes: {polygon: false, point: false, line: true} + bounds: [0, 0, 3, 3], + featureTypes: {polygon: false, point: false, line: true}, + meanCenters: [ + [0.5, 0.5], + [2.5, 2.5] + ] }; const expectedMultiLineBinaryGeometry = { @@ -118,11 +129,14 @@ const expectedMultiLineBinaryGeometry = { lines: { ...BINARY_GEOMETRY_TEMPLATE, type: 'LineString', - globalFeatureIds: {value: new Uint32Array([0, 0, 0, 0]), size: 1}, - positions: {value: new Float64Array([1, 1, 2, 2, 3, 3, 4, 4]), size: 2}, - properties: [{index: 0}], - featureIds: {value: new Uint32Array([0, 0, 0, 0]), size: 1}, - pathIndices: {value: new Int32Array([0, 2, 4]), size: 1} + globalFeatureIds: {value: new Uint32Array([0, 0, 0, 0, 1, 1, 1, 1]), size: 1}, + positions: { + value: new Float64Array([1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8]), + size: 2 + }, + properties: [{index: 0}, {index: 1}], + featureIds: {value: new Uint32Array([0, 0, 0, 0, 1, 1, 1, 1]), size: 1}, + pathIndices: {value: new Int32Array([0, 2, 4, 6, 8]), size: 1} }, polygons: { ...BINARY_GEOMETRY_TEMPLATE, @@ -132,8 +146,12 @@ const expectedMultiLineBinaryGeometry = { } } ], - bounds: [1, 1, 4, 4], - featureTypes: {polygon: false, point: false, line: true} + bounds: [1, 1, 8, 8], + featureTypes: {polygon: false, point: false, line: true}, + meanCenters: [ + [2.5, 2.5], + [6.5, 6.5] + ] }; const expectedPolygonBinaryGeometry = { @@ -153,22 +171,29 @@ const expectedPolygonBinaryGeometry = { ...BINARY_GEOMETRY_TEMPLATE, type: 'Polygon', globalFeatureIds: { - value: new Uint32Array([0, 0, 0, 0]), + value: new Uint32Array([0, 0, 0, 0, 0, 1, 1, 1, 1, 1]), size: 1 }, positions: { - value: new Float64Array([0, 0, 1, 1, 2, 2, 0, 0]), + value: new Float64Array([ + 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 10, 10, 10, 11, 11, 11, 11, 10, 10, 10 + ]), size: 2 }, - properties: [{index: 0}], - featureIds: {value: new Uint32Array([0, 0, 0, 0]), size: 1}, - polygonIndices: {value: new Int32Array([0, 4]), size: 1}, - primitivePolygonIndices: {value: new Int32Array([0, 4]), size: 1} + properties: [{index: 0}, {index: 1}], + featureIds: {value: new Uint32Array([0, 0, 0, 0, 0, 1, 1, 1, 1, 1]), size: 1}, + polygonIndices: {value: new Int32Array([0, 5, 10]), size: 1}, + primitivePolygonIndices: {value: new Int32Array([0, 5, 10]), size: 1}, + triangles: {value: new Uint32Array([1, 4, 3, 3, 2, 1, 6, 9, 8, 8, 7, 6]), size: 1} } } ], - bounds: [0, 0, 2, 2], - featureTypes: {polygon: true, point: false, line: false} + bounds: [0, 0, 11, 11], + featureTypes: {polygon: true, point: false, line: false}, + meanCenters: [ + [0.5, 0.5], + [10.5, 10.5] + ] }; const expectedMultiPolygonBinaryGeometry = { @@ -201,12 +226,14 @@ const expectedMultiPolygonBinaryGeometry = { size: 1 }, polygonIndices: {value: new Int32Array([0, 5, 10]), size: 1}, - primitivePolygonIndices: {value: new Int32Array([0, 5, 10]), size: 1} + primitivePolygonIndices: {value: new Int32Array([0, 5, 10]), size: 1}, + triangles: {value: new Uint32Array([1, 4, 3, 3, 2, 1, 6, 9, 8, 8, 7, 6]), size: 1} } } ], bounds: [0, 0, 3, 3], - featureTypes: {polygon: true, point: false, line: false} + featureTypes: {polygon: true, point: false, line: false}, + meanCenters: [[1.5, 1.5]] }; const expectedMultiPolygonHolesBinaryGeometry = { @@ -249,12 +276,24 @@ const expectedMultiPolygonHolesBinaryGeometry = { }, // NOTE: should polygonIndices be [0, 15, 30] or [0, 10, 15, 25, 30]? polygonIndices: {value: new Int32Array([0, 5, 10, 15, 20, 25, 30]), size: 1}, - primitivePolygonIndices: {value: new Int32Array([0, 5, 10, 15, 20, 25, 30]), size: 1} + primitivePolygonIndices: {value: new Int32Array([0, 5, 10, 15, 20, 25, 30]), size: 1}, + triangles: { + value: new Uint32Array([ + 4, 5, 6, 8, 5, 4, 1, 4, 6, 8, 4, 3, 2, 1, 6, 7, 8, 3, 2, 6, 7, 7, 3, 2, 11, 14, 13, 13, + 12, 11, 19, 20, 21, 23, 20, 19, 16, 19, 21, 23, 19, 18, 17, 16, 21, 22, 23, 18, 17, 21, + 22, 22, 18, 17, 26, 29, 28, 28, 27, 26 + ]), + size: 1 + } } } ], bounds: [0, 0, 13, 13], - featureTypes: {polygon: true, point: false, line: false} + featureTypes: {polygon: true, point: false, line: false}, + meanCenters: [ + [1.1666666666666667, 1.1666666666666667], + [11.166666666666666, 11.166666666666666] + ] }; test('ArrowUtils#getBinaryGeometriesFromArrow', (t) => { @@ -289,18 +328,21 @@ async function testGetBinaryGeometriesFromArrow( t.equal(arrowTable.shape, 'arrow-table'); - const table = arrowTable.data as ApacheArrowTable; - const geoColumn = table.getChild('geometry'); - t.notEqual(geoColumn, null, 'geoColumn is not null'); + if (arrowTable.shape === 'arrow-table') { + const table = arrowTable.data; + const geoColumn = table.getChild('geometry'); + t.notEqual(geoColumn, null, 'geoColumn is not null'); - const schema = serializeArrowSchema(table.schema); - const geometryColumns = getGeometryColumnsFromSchema(schema); - const encoding = geometryColumns.geometry.encoding; + const schema = serializeArrowSchema(table.schema); + const geometryColumns = getGeometryColumnsFromSchema(schema); + const encoding = geometryColumns.geometry.encoding; - t.notEqual(encoding, undefined, 'encoding is not undefined'); - if (geoColumn && encoding) { - const binaryData = getBinaryGeometriesFromArrow(geoColumn, encoding); - t.deepEqual(binaryData, expectedBinaryGeometries, 'binary geometries are correct'); + t.notEqual(encoding, undefined, 'encoding is not undefined'); + if (geoColumn && encoding) { + const options = {meanCenter: true}; + const binaryData = getBinaryGeometriesFromArrow(geoColumn, encoding, options); + t.deepEqual(binaryData, expectedBinaryGeometries, 'binary geometries are correct'); + } } return Promise.resolve(); diff --git a/modules/arrow/test/geoarrow/convert-geoarrow-to-geojson.spec.ts b/modules/arrow/test/geoarrow/convert-geoarrow-to-geojson.spec.ts index d38721b292..d897d8a447 100644 --- a/modules/arrow/test/geoarrow/convert-geoarrow-to-geojson.spec.ts +++ b/modules/arrow/test/geoarrow/convert-geoarrow-to-geojson.spec.ts @@ -25,10 +25,21 @@ const GEOARROW_ENCODINGS = [ 'geoarrow.wkt' ]; -// a simple geojson contains one point +// a simple geojson contains two points const expectedPointGeojson: FeatureCollection = { type: 'FeatureCollection', features: [ + { + type: 'Feature', + properties: { + id: 1, + name: 'name1' + }, + geometry: { + type: 'Point', + coordinates: [1, 1] + } + }, { type: 'Feature', properties: { @@ -37,13 +48,13 @@ const expectedPointGeojson: FeatureCollection = { }, geometry: { type: 'Point', - coordinates: [1, 1] + coordinates: [2, 2] } } ] }; -// a simple geojson contains one linestring +// a simple geojson contains two linestrings const expectedLineStringGeoJson: FeatureCollection = { type: 'FeatureCollection', features: [ @@ -60,11 +71,25 @@ const expectedLineStringGeoJson: FeatureCollection = { [1, 1] ] } + }, + { + type: 'Feature', + properties: { + id: 2, + name: 'name2' + }, + geometry: { + type: 'LineString', + coordinates: [ + [2, 2], + [3, 3] + ] + } } ] }; -// a simple geojson contains one polygon +// a simple geojson contains two polygons const expectedPolygonGeojson: FeatureCollection = { type: 'FeatureCollection', features: [ @@ -79,25 +104,45 @@ const expectedPolygonGeojson: FeatureCollection = { coordinates: [ [ [0, 0], + [0, 1], [1, 1], - [2, 2], + [1, 0], [0, 0] ] ] } + }, + { + type: 'Feature', + properties: { + id: 2, + name: 'name2' + }, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [10, 10], + [10, 11], + [11, 11], + [11, 10], + [10, 10] + ] + ] + } } ] }; -// a simple geojson contains one MultiPoint +// a simple geojson contains two MultiPoints const expectedMultiPointGeoJson: FeatureCollection = { type: 'FeatureCollection', features: [ { type: 'Feature', properties: { - id: 2, - name: 'name2' + id: 1, + name: 'name1' }, geometry: { type: 'MultiPoint', @@ -106,19 +151,33 @@ const expectedMultiPointGeoJson: FeatureCollection = { [2, 2] ] } + }, + { + type: 'Feature', + properties: { + id: 2, + name: 'name2' + }, + geometry: { + type: 'MultiPoint', + coordinates: [ + [3, 3], + [4, 4] + ] + } } ] }; -// a simple geojson contains one MultiLinestring +// a simple geojson contains two MultiLinestrings const expectedMultiLineStringGeoJson: FeatureCollection = { type: 'FeatureCollection', features: [ { type: 'Feature', properties: { - id: 2, - name: 'name2' + id: 1, + name: 'name1' }, geometry: { type: 'MultiLineString', @@ -133,6 +192,26 @@ const expectedMultiLineStringGeoJson: FeatureCollection = { ] ] } + }, + { + type: 'Feature', + properties: { + id: 2, + name: 'name2' + }, + geometry: { + type: 'MultiLineString', + coordinates: [ + [ + [5, 5], + [6, 6] + ], + [ + [7, 7], + [8, 8] + ] + ] + } } ] }; diff --git a/yarn.lock b/yarn.lock index b98616995e..095a409763 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2352,7 +2352,7 @@ "@babel/runtime" "^7.12.0" "@math.gl/core" "4.0.0" -"@math.gl/polygon@^4.0.0": +"@math.gl/polygon@4.0.0", "@math.gl/polygon@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@math.gl/polygon/-/polygon-4.0.0.tgz#17d2b1c7569d5a7fd1cde67e885d77e5742c23ec" integrity sha512-BsseetloYtSZkphH5Fqn02uCL9UWsD26DNLfGhvd2farhU9BaJnn0JGuZnRWT/rf+glZZcDJkyqHq5pDnSX/BQ==