Skip to content

Commit

Permalink
Flatten simple geometry collections (#42)
Browse files Browse the repository at this point in the history
* Add failing test for GeometryCollection simplifcation

* Simplify GeometryCollections containing a single Polygon

GeoJSONLint complains about these types of features:

> GeometryCollection with a single geometry should be avoided in favor
> of single part or a single object of multi-part type

This simultaneously expands (`GeometryCollection | Polygon`) and narrows
(`GeometryCollection<Polygon | LineString>`) the type of GeoJSON
features created from isolines.

* Update CHANGELOG

* 1.2.0
  • Loading branch information
mojodna authored Nov 14, 2024
1 parent 8faefe0 commit a9fc010
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 14 deletions.
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
# 1.1.0
# Changes

## 1.2.0

### ✨ Features and improvements

- Generate individual Polygons rather than GeometryCollections when isoline features contain single
geometries

## 1.1.0

### ✨ Features and improvements

Expand Down
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,11 +266,13 @@ const featureCollections = calculateRoutesResponseToFeatureCollections(response)

### calculateIsolinesResponseToFeatureCollection

This converts a CalculateIsolineResponse from the standalone Routes SDK to a GeoJSON FeatureCollection which contains one Feature for each isoline
in the response. Isolines can contain both polygons for isoline regions and lines for connectors between regions
(such as ferry travel), so each Feature is a GeometryCollection that can contain a mix of Polygons and LineStrings.
The `flattenProperties` option will flatten the nested response data into a flat properties list.
This option is enabled by default, as it makes the data easier to use from within MapLibre expressions.
This converts a CalculateIsolineResponse from the standalone Routes SDK to a GeoJSON
FeatureCollection which contains one Feature for each isoline in the response. Isolines can contain
both polygons for isoline regions and lines for connectors between regions (such as ferry travel),
so each Feature contains either a GeometryCollection with a mix of Polygons and LineStrings or a
single Polygon. The `flattenProperties` option will flatten the nested response data into a flat
properties list. This option is enabled by default, as it makes the data easier to use from within
MapLibre expressions.

Any feature that is missing its geometry in the response or has invalid geometry will throw an Error().

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@aws/amazon-location-utilities-datatypes",
"description": "Amazon Location Utilities - Data Types for JavaScript",
"license": "Apache-2.0",
"version": "1.1.0",
"version": "1.2.0",
"keywords": [],
"author": {
"name": "Amazon Web Services",
Expand Down
53 changes: 53 additions & 0 deletions src/to-geojson/georoutes-converter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1710,6 +1710,59 @@ describe("calculateIsolinesResponseToFeatureCollection", () => {

expect(calculateIsolinesResponseToFeatureCollection(input, { flattenProperties: true })).toEqual(expectedResult);
});

it("should return a Polygon (not a GeometryCollection) if no LineString is produced", () => {
const input: CalculateIsolinesResponse = {
IsolineGeometryFormat: "FlexiblePolyline",
PricingBucket: "bucket",
Isolines: [
{
Connections: [],
Geometries: [
{
PolylinePolygon: [
encodeFromLngLatArray([
[0, 0],
[10, 0],
[10, 10],
[0, 10],
[0, 0],
]),
],
},
],
TimeThreshold: 1000,
},
],
};

const expectedResult: FeatureCollection = {
type: "FeatureCollection",
features: [
{
type: "Feature",
id: 0,
geometry: {
type: "Polygon",
coordinates: [
[
[0, 0],
[10, 0],
[10, 10],
[0, 10],
[0, 0],
],
],
},
properties: {
TimeThreshold: 1000,
},
},
],
};

expect(calculateIsolinesResponseToFeatureCollection(input, { flattenProperties: false })).toEqual(expectedResult);
});
});

describe("optimizeWaypointsResponseToFeatureCollection", () => {
Expand Down
24 changes: 19 additions & 5 deletions src/to-geojson/georoutes-converter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,8 @@ const defaultCalculateIsolinesResponseOptions = defaultBaseGeoRoutesOptions;
/**
* This converts a CalculateIsolineResponse to a GeoJSON FeatureCollection which contains one Feature for each isoline
* in the response. Isolines can contain both polygons for isoline regions and lines for connectors between regions
* (such as ferry travel), so each Feature is a GeometryCollection that can contain a mix of Polygons and LineStrings.
* (such as ferry travel), so each Feature contains either a GeometryCollection with a mix of Polygons and LineStrings
* or a single Polygon.
*
* Any feature that is missing its geometry in the response or has invalid geometry will throw an Error.
*
Expand Down Expand Up @@ -405,11 +406,11 @@ const defaultCalculateIsolinesResponseOptions = defaultBaseGeoRoutesOptions;
export function calculateIsolinesResponseToFeatureCollection(
isolinesResponse: CalculateIsolinesResponse,
options?: CalculateIsolinesResponseOptions,
): FeatureCollection<GeometryCollection> {
): FeatureCollection<GeometryCollection<Polygon | LineString> | Polygon> {
// Set any options that weren't passed in to the default values.
options = { ...defaultCalculateIsolinesResponseOptions, ...options };

const isolines: FeatureCollection<GeometryCollection> = {
const isolines: FeatureCollection<GeometryCollection<Polygon | LineString> | Polygon> = {
type: "FeatureCollection",
features: [],
};
Expand All @@ -420,7 +421,7 @@ export function calculateIsolinesResponseToFeatureCollection(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { Geometries, Connections, ...properties } = isoline;

const feature: Feature<GeometryCollection> = {
const feature: Feature<GeometryCollection<Polygon | LineString>> = {
type: "Feature",
id: isolines.features.length,
properties: options.flattenProperties ? flattenProperties(properties, "") : properties,
Expand Down Expand Up @@ -448,7 +449,20 @@ export function calculateIsolinesResponseToFeatureCollection(

// As long as this feature has at least one polygon or line, add it to the result set.
if (feature.geometry.geometries.length > 0) {
isolines.features.push(feature);
if (feature.geometry.geometries.length === 1 && feature.geometry.geometries[0].type === "Polygon") {
// GeometryCollections containing single geometries trigger GeoJSONLint warnings:
// GeometryCollection with a single geometry should be avoided in favor of single part
// or a single object of multi-part type
// in practice, the geometry type for single-geometry isolines is Polygon; LineStrings are
// supplemental and appear when multiple polygons are present, representing the connection
// between those areas
isolines.features.push({
...feature,
geometry: feature.geometry.geometries[0],
});
} else {
isolines.features.push(feature);
}
}
}

Expand Down

0 comments on commit a9fc010

Please sign in to comment.