Skip to content

Commit

Permalink
feat: Migrate to Turf v7. (#269)
Browse files Browse the repository at this point in the history
  • Loading branch information
parkerziegler authored Jun 23, 2024
1 parent bf428be commit 03dcb6f
Show file tree
Hide file tree
Showing 8 changed files with 227 additions and 1,321 deletions.
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@
"@codemirror/language": "^6.10.1",
"@codemirror/view": "^6.26.3",
"@lezer/highlight": "^1.2.0",
"@turf/turf": "^6.5.0",
"@turf/bbox": "^7.0.0",
"@turf/boolean-point-in-polygon": "^7.0.0",
"@turf/centroid": "^7.0.0",
"@turf/helpers": "^7.0.0",
"@turf/random": "^7.0.0",
"classnames": "^2.3.2",
"codemirror": "^6.0.1",
"d3": "^7.8.2",
Expand Down
1,380 changes: 132 additions & 1,248 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/lib/components/data/TransformationEditor.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import * as turf from '@turf/turf';
import { featureCollection } from '@turf/helpers';
import { EditorView } from 'codemirror';
import { onDestroy } from 'svelte';
Expand Down Expand Up @@ -103,7 +103,7 @@
if ($selectedFeature) {
transformationWorker(
program,
turf.featureCollection([$selectedFeature]),
featureCollection([$selectedFeature]),
(message) => {
// TODO: Split out the additional edge cases here.
// - message.data?.[0] is strictly a GeoJSON feature.
Expand Down
85 changes: 47 additions & 38 deletions src/lib/interaction/geometry.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import * as turf from '@turf/turf';
import { bbox } from '@turf/bbox';
import { booleanPointInPolygon } from '@turf/boolean-point-in-polygon';
import { centroid } from '@turf/centroid';
import { feature, featureCollection } from '@turf/helpers';
import { randomPoint } from '@turf/random';
import * as d3 from 'd3';
import type { Feature, FeatureCollection } from 'geojson';
import type {
Feature,
FeatureCollection,
Point,
MultiPolygon,
Polygon
} from 'geojson';
import type { ExpressionSpecification } from 'maplibre-gl';

import type { CartoKitProportionalSymbolLayer } from '$lib/types/CartoKitLayer';
Expand All @@ -10,7 +20,7 @@ import type { CartoKitProportionalSymbolLayer } from '$lib/types/CartoKitLayer';
*
* @param layer – The CartoKitProportionalSymbolLayer to derive a radius scale for.
*
* @returnsa MapLibre GL JS expression for a proportional symbol radius scale.
* @returnsA MapLibre GL JS expression for a proportional symbol radius scale.
*/
export function deriveSize(
layer: CartoKitProportionalSymbolLayer
Expand Down Expand Up @@ -42,68 +52,67 @@ export function deriveSize(
/**
* Derive centroids for a set of GeoJSON features.
*
* @param features – the input features to derive centroids for.
* @param features – The input features to derive centroids for.
*
* @returnsa FeatureCollection of centroids.
* @returnsA FeatureCollection of centroids.
*/
export function deriveCentroids(features: Feature[]): FeatureCollection {
const feats = features.map((feature) => {
const centroid = turf.centroid(feature);

return turf.feature(centroid.geometry, feature.properties);
});

return turf.featureCollection(feats);
return featureCollection(
features.map(({ geometry, properties }) =>
feature(centroid(geometry).geometry, properties)
)
);
}

interface GenerateDotDensityPointsParams {
features: Feature[];
features: Feature<Polygon | MultiPolygon>[];
attribute: string;
value: number;
}

/**
* Generate dots for a dot density layer.
*
* @param features – the polygon features within which to generate dots.
* @param attribute – the attribute being visualized.
* @param value – the value of dots to attribute value.
* @param features – The polygon features within which to generate dots.
* @param attribute – The attribute being visualized.
* @param value – The dot value of the dot density layer.
*
* @returnsa FeatureCollection of dots.
* @returnsA FeatureCollection of dots.
*/
export function generateDotDensityPoints({
features,
attribute,
value
}: GenerateDotDensityPointsParams): FeatureCollection {
const dots = features.flatMap((feature) => {
const numPoints = Math.floor(feature.properties?.[attribute] / value) ?? 0;
}: GenerateDotDensityPointsParams): FeatureCollection<Point> {
return featureCollection(
features.flatMap(({ geometry, properties }) => {
const numPoints = Math.floor(properties?.[attribute] / value) ?? 0;

// Obtain the bounding box of the polygon.
const bbox = turf.bbox(feature);
// Obtain the bounding box of the polygon.
const boundingBox = bbox(geometry);

// Begin "throwing" random points within the bounding box,
// keeping them only if they fall within the polygon.
const selectedFeatures: Feature[] = [];
while (selectedFeatures.length < numPoints) {
const candidate = turf.randomPoint(1, { bbox }).features[0];
// Begin "throwing" random points within the bounding box, keeping them only
// if they fall within the polygon.
const selectedFeatures: Feature<Point>[] = [];

if (turf.booleanWithin(candidate, feature)) {
selectedFeatures.push(candidate);
}
}
while (selectedFeatures.length < numPoints) {
const candidate = randomPoint(1, { bbox: boundingBox }).features[0];

return selectedFeatures.flatMap((point) => {
return turf.feature(point.geometry, feature.properties);
});
});
if (booleanPointInPolygon(candidate, geometry)) {
selectedFeatures.push(candidate);
}
}

return turf.featureCollection(dots);
return selectedFeatures.map((point) =>
feature(point.geometry, properties)
);
})
);
}

/**
* Derive a starting dot value for a dot density layer. This value represents the ratio
* of dots to data value, e.g., 1 dot = 100 people.
* Derive a starting dot value for a dot density layer. This value represents
* the ratio of dots to data value, e.g., 1 dot = 100 people.
*
* @param features – The GeoJSON features to derive a dot density value for.
* @param attribute – The data attribute.
Expand Down
18 changes: 13 additions & 5 deletions src/lib/interaction/map-type.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Geometry } from 'geojson';
import type { Geometry, Feature, Polygon, MultiPolygon } from 'geojson';
import type { Map, GeoJSONSource } from 'maplibre-gl';

import { deriveColorScale } from '$lib/interaction/color';
Expand Down Expand Up @@ -491,7 +491,9 @@ const transitionToDotDensity = (
generateUnsupportedTransitionError(rawGeometryType, 'Polygon');
}

const features = layer.data.rawGeoJSON.features;
const features = layer.data.rawGeoJSON.features as Feature<
Polygon | MultiPolygon
>[];
const attribute = selectNumericAttribute(features);
const dotValue = deriveDotDensityStartingValue(features, attribute);

Expand Down Expand Up @@ -534,7 +536,9 @@ const transitionToDotDensity = (
generateUnsupportedTransitionError(rawGeometryType, 'Polygon');
}

const features = layer.data.rawGeoJSON.features;
const features = layer.data.rawGeoJSON.features as Feature<
Polygon | MultiPolygon
>[];
const attribute = layer.style.size.attribute;
const dotValue = deriveDotDensityStartingValue(
features,
Expand Down Expand Up @@ -588,7 +592,9 @@ const transitionToDotDensity = (
throw error;
}
case 'Fill': {
const features = layer.data.geoJSON.features;
const features = layer.data.geoJSON.features as Feature<
Polygon | MultiPolygon
>[];
const attribute = selectNumericAttribute(features);
const dotValue = deriveDotDensityStartingValue(features, attribute);

Expand Down Expand Up @@ -625,7 +631,9 @@ const transitionToDotDensity = (
};
}
case 'Choropleth': {
const features = layer.data.geoJSON.features;
const features = layer.data.geoJSON.features as Feature<
Polygon | MultiPolygon
>[];
const attribute = layer.style.fill.attribute;
const dotValue = deriveDotDensityStartingValue(features, attribute);

Expand Down
15 changes: 12 additions & 3 deletions src/lib/interaction/update.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import type { FeatureCollection } from 'geojson';
import type {
FeatureCollection,
Feature,
Polygon,
MultiPolygon
} from 'geojson';
import type { GeoJSONSource } from 'maplibre-gl';
import { get } from 'svelte/store';

Expand Down Expand Up @@ -279,7 +284,9 @@ export const dispatchLayerUpdate = ({
);

const features = generateDotDensityPoints({
features: lyr.data.rawGeoJSON.features,
features: lyr.data.rawGeoJSON.features as Feature<
Polygon | MultiPolygon
>[],
attribute: payload.attribute,
value: dotValue
});
Expand Down Expand Up @@ -711,7 +718,9 @@ export const dispatchLayerUpdate = ({
// conversely, the current layer geometry will be points, which do not
// allow us to generate a dot density.
const features = generateDotDensityPoints({
features: lyr.data.rawGeoJSON.features,
features: lyr.data.rawGeoJSON.features as Feature<
Polygon | MultiPolygon
>[],
attribute: lyr.style.dots.attribute,
value: payload.value
});
Expand Down
22 changes: 7 additions & 15 deletions src/lib/utils/geojson.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as turf from '@turf/turf';
import { feature, featureCollection } from '@turf/helpers';
import type { GeoJSON, FeatureCollection, Geometry } from 'geojson';

import { isPropertyNumeric } from '$lib/utils/property';
Expand All @@ -18,22 +18,14 @@ export function normalizeGeoJSONToFeatureCollection(
case 'LineString':
case 'MultiLineString':
case 'Polygon':
case 'MultiPolygon': {
const feature = turf.feature(geoJSON);
const featureCollection = turf.featureCollection([feature]);

return featureCollection;
}
case 'GeometryCollection': {
const features = geoJSON.geometries.map((geometry) =>
turf.feature(geometry)
case 'MultiPolygon':
return featureCollection([feature(geoJSON)]);
case 'GeometryCollection':
return featureCollection(
geoJSON.geometries.map((geometry) => feature(geometry))
);
const featureCollection = turf.featureCollection(features);

return featureCollection;
}
case 'Feature':
return turf.featureCollection([geoJSON]);
return featureCollection([geoJSON]);
case 'FeatureCollection':
return geoJSON;
}
Expand Down
18 changes: 9 additions & 9 deletions src/lib/utils/transformation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ export function transformGeometryToCentroids(): Transformation {
name: 'transformGeometryToCentroids',
definition: `
function transformGeometryToCentroids(geoJSON) {
const centroids = geoJSON.features.map((feature) => {
return turf.feature(turf.centroid(feature).geometry, feature.properties);
});
return turf.featureCollection(centroids)
return turf.featureCollection(
geoJSON.features.map((feature) =>
turf.feature(turf.centroid(feature).geometry, feature.properties);
)
);
}`,
type: 'geometric'
};
Expand All @@ -34,14 +34,14 @@ export function transformDotDensity(
while (selectedFeatures.length < numPoints) {
const candidate = turf.randomPoint(1, { bbox }).features[0];
if (turf.booleanWithin(candidate, feature)) {
if (turf.booleanPointInPolygon(candidate, feature)) {
selectedFeatures.push(candidate);
}
}
return selectedFeatures.flatMap((point) => {
return turf.feature(point.geometry, feature.properties);
});
return selectedFeatures.map((point) =>
turf.feature(point.geometry, feature.properties)
);
});
return turf.featureCollection(dots);
Expand Down

0 comments on commit 03dcb6f

Please sign in to comment.