Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(Arrow): add test cases for multipolygon with holes #2782

Merged
merged 3 commits into from
Nov 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,8 @@ export function getBinaryGeometriesFromArrow(
...BINARY_GEOMETRY_TEMPLATE,
...(featureTypes.polygon ? binaryContent : {}),
polygonIndices: {
// TODO why deck.gl's tessellatePolygon performance is not good when using geometryIndicies
// even when there is no hole in any polygon
// 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.
value: featureTypes.polygon ? geomOffset : new Uint16Array(0),
size: 1
},
Expand Down
Binary file modified modules/arrow/test/data/multipolygon_hole.arrow
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import {
LINE_ARROW_FILE,
MULTILINE_ARROW_FILE,
POLYGON_ARROW_FILE,
MULTIPOLYGON_ARROW_FILE
MULTIPOLYGON_ARROW_FILE,
MULTIPOLYGON_HOLE_ARROW_FILE
} from './convert-geoarrow-to-geojson.spec';

const expectedPointBinaryGeometry = {
Expand Down Expand Up @@ -208,14 +209,63 @@ const expectedMultiPolygonBinaryGeometry = {
featureTypes: {polygon: true, point: false, line: false}
};

const expectedMultiPolygonHolesBinaryGeometry = {
binaryGeometries: [
{
shape: 'binary-feature-collection',
points: {
...BINARY_GEOMETRY_TEMPLATE,
type: 'Point'
},
lines: {
...BINARY_GEOMETRY_TEMPLATE,
type: 'LineString',
pathIndices: {value: new Uint16Array(0), size: 1}
},
polygons: {
...BINARY_GEOMETRY_TEMPLATE,
type: 'Polygon',
globalFeatureIds: {
value: new Uint32Array([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
]),
size: 1
},
positions: {
value: new Float64Array([
0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0.25, 0.25, 0.25, 0.75, 0.75, 0.75, 0.75, 0.25, 0.25,
0.25, 2, 2, 2, 3, 3, 3, 3, 2, 2, 2, 10, 10, 10, 11, 11, 11, 11, 10, 10, 10, 10.25,
10.25, 10.25, 10.75, 10.75, 10.75, 10.75, 10.25, 10.25, 10.25, 12, 12, 12, 13, 13, 13,
13, 12, 12, 12
]),
size: 2
},
properties: [{index: 0}, {index: 1}],
featureIds: {
value: new Uint32Array([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
]),
size: 1
},
// 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}
}
}
],
bounds: [0, 0, 13, 13],
featureTypes: {polygon: true, point: false, line: false}
};

test('ArrowUtils#getBinaryGeometriesFromArrow', (t) => {
const testCases = [
[POINT_ARROW_FILE, expectedPointBinaryGeometry],
[MULTIPOINT_ARROW_FILE, expectedMultiPointBinaryGeometry],
[LINE_ARROW_FILE, expectedLineBinaryGeometry],
[MULTILINE_ARROW_FILE, expectedMultiLineBinaryGeometry],
[POLYGON_ARROW_FILE, expectedPolygonBinaryGeometry],
[MULTIPOLYGON_ARROW_FILE, expectedMultiPolygonBinaryGeometry]
[MULTIPOLYGON_ARROW_FILE, expectedMultiPolygonBinaryGeometry],
[MULTIPOLYGON_HOLE_ARROW_FILE, expectedMultiPolygonHolesBinaryGeometry]
];

testCases.forEach((testCase) => {
Expand Down
101 changes: 51 additions & 50 deletions modules/arrow/test/geoarrow/convert-geoarrow-to-geojson.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import test, {Test} from 'tape-promise/tape';

import {tableFromIPC} from 'apache-arrow';
import {fetchFile} from '@loaders.gl/core';
import {fetchFile, parse} from '@loaders.gl/core';
import {FeatureCollection} from '@loaders.gl/schema';
import {serializeArrowSchema, parseGeometryFromArrow} from '@loaders.gl/arrow';
import {ArrowLoader, serializeArrowSchema, parseGeometryFromArrow} from '@loaders.gl/arrow';
import {getGeometryColumnsFromSchema} from '@loaders.gl/gis';

export const POINT_ARROW_FILE = '@loaders.gl/arrow/test/data/point.arrow';
Expand Down Expand Up @@ -175,8 +174,7 @@ const expectedMultiPolygonGeojson: FeatureCollection = {
]
};

// a simple geojson contains one MultiPolygon with a whole in it
/*
// a simple geojson contains two MultiPolygons with a whole in it
const expectedMultiPolygonWithHoleGeojson: FeatureCollection = {
type: 'FeatureCollection',
features: [
Expand Down Expand Up @@ -256,17 +254,16 @@ const expectedMultiPolygonWithHoleGeojson: FeatureCollection = {
}
]
};
*/

test('ArrowUtils#parseGeometryFromArrow', (t) => {
const testCases: [string, FeatureCollection][] = [
[MULTIPOLYGON_HOLE_ARROW_FILE, expectedMultiPolygonWithHoleGeojson],
[POINT_ARROW_FILE, expectedPointGeojson],
[MULTIPOINT_ARROW_FILE, expectedMultiPointGeoJson],
[LINE_ARROW_FILE, expectedLineStringGeoJson],
[MULTILINE_ARROW_FILE, expectedMultiLineStringGeoJson],
[POLYGON_ARROW_FILE, expectedPolygonGeojson],
[MULTIPOLYGON_ARROW_FILE, expectedMultiPolygonGeojson]
// [MULTIPOLYGON_HOLE_ARROW_FILE, expectedMultiPolygonWithHoleGeojson]
];

testCases.forEach((testCase) => {
Expand All @@ -281,60 +278,64 @@ async function testParseFromArrow(
arrowFile: string,
expectedGeojson: FeatureCollection
): Promise<void> {
// TODO: use the following code instead of apache-arrow to load arrow table
// const arrowTable = await parse(fetchFile(arrowFile), ArrowLoader, {worker: false});
const response = await fetchFile(arrowFile);
const arrayBuffer = await response.arrayBuffer();
const arrowTable = tableFromIPC(new Uint8Array(arrayBuffer));
const arrowTable = await parse(fetchFile(arrowFile), ArrowLoader, {
worker: false,
arrow: {
shape: 'arrow-table'
}
});

t.comment(arrowFile);
t.equal(arrowTable.shape, 'arrow-table');

// check if the arrow table is loaded correctly
t.equal(
arrowTable.numRows,
expectedGeojson.features.length,
`arrow table has ${expectedGeojson.features.length} row`
);
if (arrowTable.shape === 'arrow-table') {
const table = arrowTable.data;
// check if the arrow table is loaded correctly
t.equal(
table.numRows,
expectedGeojson.features.length,
`arrow table has ${expectedGeojson.features.length} row`
);

const colNames = [...Object.keys(expectedGeojson.features[0].properties || {}), 'geometry'];
t.equal(arrowTable.numCols, colNames.length, `arrow table has ${colNames.length} columns`);
const colNames = [...Object.keys(expectedGeojson.features[0].properties || {}), 'geometry'];
t.equal(table.numCols, colNames.length, `arrow table has ${colNames.length} columns`);

// check fields exist in arrow table schema
arrowTable.schema.fields.map((field) =>
t.equal(colNames.includes(field.name), true, `arrow table has ${field.name} column`)
);
// check fields exist in arrow table schema
table.schema.fields.map((field) =>
t.equal(colNames.includes(field.name), true, `arrow table has ${field.name} column`)
);

const schema = serializeArrowSchema(arrowTable.schema);
const geometryColumns = getGeometryColumnsFromSchema(schema);
const schema = serializeArrowSchema(table.schema);
const geometryColumns = getGeometryColumnsFromSchema(schema);

// check 'geometry' is in geometryColumns (geometryColumns is a Map object)
t.equal(Boolean(geometryColumns.geometry), true, 'geometryColumns has geometry column');
// check 'geometry' is in geometryColumns (geometryColumns is a Map object)
t.equal(Boolean(geometryColumns.geometry), true, 'geometryColumns has geometry column');

// get encoding from geometryColumns['geometry']
const encoding = geometryColumns.geometry.encoding;
// get encoding from geometryColumns['geometry']
const encoding = geometryColumns.geometry.encoding;

// check encoding is one of GEOARROW_ENCODINGS
t.ok(
Object.values(GEOARROW_ENCODINGS).includes(encoding!),
'encoding is one of GEOARROW_ENCODINGS'
);
// check encoding is one of GEOARROW_ENCODINGS
t.ok(
Object.values(GEOARROW_ENCODINGS).includes(encoding!),
'encoding is one of GEOARROW_ENCODINGS'
);

// get first geometry from arrow geometry column
const firstArrowGeometry = arrowTable.getChild('geometry')?.get(0);
const firstArrowGeometryObject = {
encoding,
data: firstArrowGeometry
};
// get first geometry from arrow geometry column
const firstArrowGeometry = table.getChild('geometry')?.get(0);
const firstArrowGeometryObject = {
encoding,
data: firstArrowGeometry
};

// parse arrow geometry to geojson feature
const firstFeature = parseGeometryFromArrow(firstArrowGeometryObject);
// parse arrow geometry to geojson feature
const firstFeature = parseGeometryFromArrow(firstArrowGeometryObject);

// check if geometry in firstFeature is equal to the original geometry in expectedPointGeojson
t.deepEqual(
firstFeature?.geometry,
expectedGeojson.features[0].geometry,
'firstFeature.geometry is equal to expectedGeojson.features[0].geometry'
);
// check if geometry in firstFeature is equal to the original geometry in expectedPointGeojson
t.deepEqual(
firstFeature?.geometry,
expectedGeojson.features[0].geometry,
'firstFeature.geometry is equal to expectedGeojson.features[0].geometry'
);
}

return Promise.resolve();
}
Loading