-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add createViewportSpatialFilter()
- Loading branch information
1 parent
1e5f07b
commit ddfb4e7
Showing
5 changed files
with
2,156 additions
and
129 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
import { | ||
bboxClip, | ||
bboxPolygon, | ||
getType, | ||
polygon, | ||
multiPolygon, | ||
union, | ||
featureCollection, | ||
feature, | ||
} from '@turf/turf'; | ||
import type {BBox, MultiPolygon, Polygon, Position} from 'geojson'; | ||
import {SpatialFilter} from './types'; | ||
|
||
/** | ||
* TODO: Documentation. | ||
*/ | ||
export function createViewportSpatialFilter( | ||
// Use explicit [number, ...], not BBox. The 'geojson' package is not a | ||
// production dependency, and cannot be used in publicly exported APIs. | ||
viewport: [number, number, number, number] | ||
): SpatialFilter | undefined { | ||
if (_isGlobalViewport(viewport)) { | ||
return; | ||
} | ||
|
||
const spatialFilter = _normalizeGeometry(bboxPolygon(viewport).geometry); | ||
if (spatialFilter) { | ||
return spatialFilter as SpatialFilter; | ||
} | ||
|
||
return undefined; | ||
} | ||
|
||
/** | ||
* Check if a viewport is large enough to represent a global coverage. | ||
* In this case the spatial filter parameter for widget calculation is removed. | ||
* | ||
* @internalRemarks Source: @carto/react-core | ||
*/ | ||
function _isGlobalViewport(viewport: BBox) { | ||
const [minx, miny, maxx, maxy] = viewport; | ||
return maxx - minx > 179.5 * 2 && maxy - miny > 85.05 * 2; | ||
} | ||
|
||
/** | ||
* Normalized a geometry, coming from a mask or a viewport. The parts | ||
* spanning outside longitude range [-180, +180] are clipped and "folded" | ||
* back to the valid range and unioned to the polygons inide that range. | ||
* | ||
* It results in a Polygon or MultiPolygon strictly inside the validity range. | ||
* | ||
* @internalRemarks Source: @carto/react-core | ||
*/ | ||
function _normalizeGeometry( | ||
geometry: Polygon | MultiPolygon | ||
): Polygon | MultiPolygon | null { | ||
const WORLD = [-180, -90, +180, +90] as BBox; | ||
const worldClip = _clean( | ||
bboxClip(geometry, WORLD).geometry as Polygon | MultiPolygon | ||
); | ||
|
||
const geometryTxWest = _tx(geometry, 360); | ||
const geometryTxEast = _tx(geometry, -360); | ||
|
||
let result: Polygon | MultiPolygon | null = worldClip; | ||
|
||
if (result && geometryTxWest) { | ||
const worldWestClip = _clean( | ||
bboxClip(geometryTxWest, WORLD).geometry as Polygon | MultiPolygon | ||
); | ||
if (worldWestClip) { | ||
const collection = featureCollection([ | ||
feature(result), | ||
feature(worldWestClip), | ||
]); | ||
const merged = union(collection); | ||
result = merged ? _clean(merged.geometry) : result; | ||
} | ||
} | ||
|
||
if (result && geometryTxEast) { | ||
const worldEastClip = _clean( | ||
bboxClip(geometryTxEast, WORLD).geometry as Polygon | MultiPolygon | ||
); | ||
if (worldEastClip) { | ||
const collection = featureCollection([ | ||
feature(result), | ||
feature(worldEastClip), | ||
]); | ||
const merged = union(collection); | ||
result = merged ? _clean(merged.geometry) : result; | ||
} | ||
} | ||
|
||
return result; | ||
} | ||
|
||
/** @internalRemarks Source: @carto/react-core */ | ||
function _cleanPolygonCoords(cc: Position[][]) { | ||
const coords = cc.filter((c) => c.length > 0); | ||
return coords.length > 0 ? coords : null; | ||
} | ||
|
||
/** @internalRemarks Source: @carto/react-core */ | ||
function _cleanMultiPolygonCoords(ccc: Position[][][]) { | ||
const coords = ccc.map(_cleanPolygonCoords).filter((cc) => cc); | ||
return coords.length > 0 ? coords : null; | ||
} | ||
|
||
/** @internalRemarks Source: @carto/react-core */ | ||
function _clean( | ||
geometry: Polygon | MultiPolygon | ||
): Polygon | MultiPolygon | null { | ||
if (!geometry) { | ||
return null; | ||
} else if (getType(geometry) === 'Polygon') { | ||
const coords = _cleanPolygonCoords((geometry as Polygon).coordinates); | ||
return coords ? polygon(coords).geometry : null; | ||
} else if (getType(geometry) === 'MultiPolygon') { | ||
const coords = _cleanMultiPolygonCoords( | ||
(geometry as MultiPolygon).coordinates | ||
); | ||
return coords ? multiPolygon(coords as Position[][][]).geometry : null; | ||
} else { | ||
return null; | ||
} | ||
} | ||
|
||
/** @internalRemarks Source: @carto/react-core */ | ||
function _txContourCoords(cc: Position[], distance: number) { | ||
return cc.map((c) => [c[0] + distance, c[1]]); | ||
} | ||
|
||
/** @internalRemarks Source: @carto/react-core */ | ||
function _txPolygonCoords(ccc: Position[][], distance: number) { | ||
return ccc.map((cc) => _txContourCoords(cc, distance)); | ||
} | ||
|
||
/** @internalRemarks Source: @carto/react-core */ | ||
function _txMultiPolygonCoords(cccc: Position[][][], distance: number) { | ||
return cccc.map((ccc) => _txPolygonCoords(ccc, distance)); | ||
} | ||
|
||
/** @internalRemarks Source: @carto/react-core */ | ||
function _tx(geometry: Polygon | MultiPolygon, distance: number) { | ||
if (geometry && getType(geometry) === 'Polygon') { | ||
const coords = _txPolygonCoords( | ||
(geometry as Polygon).coordinates, | ||
distance | ||
); | ||
return polygon(coords).geometry; | ||
} else if (geometry && getType(geometry) === 'MultiPolygon') { | ||
const coords = _txMultiPolygonCoords( | ||
(geometry as MultiPolygon).coordinates, | ||
distance | ||
); | ||
return multiPolygon(coords).geometry; | ||
} else { | ||
return null; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
export * from './client.js'; | ||
export * from './constants.js'; | ||
export * from './filters.js'; | ||
export * from './geo.js'; | ||
export * from './sources/index.js'; | ||
export * from './types.js'; |
Oops, something went wrong.