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

Ensure polygons are inside -180/180 range before making stac request #734

Merged
merged 3 commits into from
Nov 9, 2023
Merged
Show file tree
Hide file tree
Changes from 2 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
30 changes: 24 additions & 6 deletions app/scripts/components/analysis/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { endOfDay, startOfDay, format } from 'date-fns';
import { Feature, FeatureCollection, MultiPolygon, Polygon } from 'geojson';
import { userTzDate2utcString } from '$utils/date';
import { fixAntimeridian } from '$utils/antimeridian';

/**
* Creates the appropriate filter object to send to STAC.
Expand All @@ -16,6 +17,8 @@ export function getFilterPayload(
aoi: FeatureCollection<Polygon>,
collections: string[]
) {
const aoiMultiPolygon = fixAoiFcForStacSearch(aoi);

const filterPayload = {
op: 'and',
args: [
Expand All @@ -31,11 +34,9 @@ export function getFilterPayload(
}
]
},
// Stac search spatial intersect needs to be done on a single feature.
// Using a Multipolygon
{
op: 's_intersects',
args: [{ property: 'geometry' }, combineFeatureCollection(aoi).geometry]
args: [{ property: 'geometry' }, aoiMultiPolygon.geometry]
},
{
op: 'in',
Expand All @@ -50,9 +51,9 @@ export function getFilterPayload(
* Converts a MultiPolygon to a Feature Collection of polygons.
*
* @param feature MultiPolygon feature
*
*
* @see combineFeatureCollection() for opposite
*
*
* @returns Feature Collection of Polygons
*/
export function multiPolygonToPolygons(feature: Feature<MultiPolygon>) {
Expand All @@ -75,7 +76,7 @@ export function multiPolygonToPolygons(feature: Feature<MultiPolygon>) {
* Converts a Feature Collection of polygons into a MultiPolygon
*
* @param featureCollection Feature Collection of Polygons
*
*
* @see multiPolygonToPolygons() for opposite
*
* @returns MultiPolygon Feature
Expand All @@ -95,6 +96,23 @@ export function combineFeatureCollection(
};
}

/**
* Fixes the AOI feature collection for a STAC search by converting all polygons
* to a single multipolygon and ensuring that every polygon is inside the
* -180/180 range.
* @param aoi The AOI feature collection
* @returns AOI as a multipolygon with every polygon inside the -180/180 range
*/
export function fixAoiFcForStacSearch(aoi: FeatureCollection<Polygon>) {
// Stac search spatial intersect needs to be done on a single feature.
// Using a Multipolygon
const singleMultiPolygon = combineFeatureCollection(aoi);
// And every polygon must be inside the -180/180 range.
// See: https://github.com/NASA-IMPACT/veda-ui/issues/732
const aoiMultiPolygon = fixAntimeridian(singleMultiPolygon);
return aoiMultiPolygon;
}

export function getDateRangeFormatted(startDate, endDate) {
const dFormat = 'yyyy-MM-dd';
const startDateFormatted = format(startDate, dFormat);
Expand Down
8 changes: 7 additions & 1 deletion app/scripts/components/common/map/map-component.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import React, { useCallback, ReactElement, useMemo } from 'react';
import ReactMapGlMap from 'react-map-gl';
import ReactMapGlMap, { LngLatBoundsLike } from 'react-map-gl';
import { ProjectionOptions } from 'veda';
import 'mapbox-gl/dist/mapbox-gl.css';
import 'mapbox-gl-compare/dist/mapbox-gl-compare.css';
import { convertProjectionToMapbox } from '../mapbox/map-options/utils';
import useMapStyle from './hooks/use-map-style';
import { useMapsContext } from './hooks/use-maps';

const maxMapBounds: LngLatBoundsLike = [
[-540, -90], // SW
[540, 90] // NE
];

export default function MapComponent({
controls,
isCompared,
Expand Down Expand Up @@ -52,6 +57,7 @@ export default function MapComponent({
mapStyle={style as any}
onMove={onMove}
projection={mapboxProjection}
maxBounds={maxMapBounds}
>
{controls}
</ReactMapGlMap>
Expand Down
4 changes: 2 additions & 2 deletions app/scripts/components/exploration/analysis-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
} from './types.d.ts';
import { ExtendedError } from './data-utils';
import {
combineFeatureCollection,
fixAoiFcForStacSearch,
getFilterPayload
} from '$components/analysis/utils';

Expand Down Expand Up @@ -163,7 +163,7 @@ export async function requestDatasetTimeseriesData({
const { data } = await axios.post(
`${process.env.API_RASTER_ENDPOINT}/cog/statistics?url=${url}`,
// Making a request with a FC causes a 500 (as of 2023/01/20)
combineFeatureCollection(aoi),
fixAoiFcForStacSearch(aoi),
{ signal }
);
return {
Expand Down
243 changes: 243 additions & 0 deletions app/scripts/utils/antimeridian.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
import { Feature, MultiPolygon, Polygon } from 'geojson';
import { fixAntimeridian } from './antimeridian';

describe('Antimeridian', () => {
it('Feature in 1st earth east', () => {
const feature: Feature<Polygon> = {
type: 'Feature',
properties: {},
geometry: {
coordinates: [
[
[190, 20],
[190, -20],
[200, -20],
[200, 20],
[190, 20]
]
],
type: 'Polygon'
}
};

const expected: Feature<MultiPolygon> = {
type: 'Feature',
properties: {},
geometry: {
coordinates: [
[
[
[-170, -20],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you point me where the order of the coordinates changes? I can't wrap my head around it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was also strange to me, but for some reason the turf.js function (intersect and difference), reorder the coordinates when cutting the polygons.

[-160, -20],
[-160, 20],
[-170, 20],
[-170, -20]
]
]
],
type: 'MultiPolygon'
}
};

const result = fixAntimeridian(feature);
expect(result).toEqual(expected);
});

it('Feature crossing 540 antimeridian earth east', () => {
const feature: Feature<Polygon> = {
type: 'Feature',
properties: {},
geometry: {
coordinates: [
[
[500, 20],
[500, -20],
[580, -20],
[580, 20],
[500, 20]
]
],
type: 'Polygon'
}
};

const expected: Feature<MultiPolygon> = {
type: 'Feature',
properties: {},
geometry: {
coordinates: [
[
[
[-180, -20],
[-140, -20],
[-140, 20],
[-180, 20],
[-180, -20]
]
],
[
[
[140, -20],
[180, -20],
[180, 20],
[140, 20],
[140, -20]
]
]
],
type: 'MultiPolygon'
}
};

const result = fixAntimeridian(feature);
expect(result).toEqual(expected);
});

it('Feature spanning several maps', () => {
const feature: Feature<Polygon> = {
type: 'Feature',
properties: {},
geometry: {
coordinates: [
[
[-360, 10],
[500, 10],
[500, -10],
[-360, -10],
[-360, 10]
]
],
type: 'Polygon'
}
};

const expected: Feature<MultiPolygon> = {
type: 'Feature',
properties: {},
geometry: {
coordinates: [
[
[
[-180, -10],
[180, -10],
[180, 10],
[-180, 10],
[-180, -10]
]
]
],
type: 'MultiPolygon'
}
};

const result = fixAntimeridian(feature);
expect(result).toEqual(expected);
});

it('Multiple features', () => {
const feature: Feature<MultiPolygon> = {
type: 'Feature',
properties: {},
geometry: {
coordinates: [
[
[
[170, 30],
[190, 30],
[190, 20],
[170, 20],
[170, 30]
]
],
[
[
[-530, 10],
[-530, 0],
[-550, 0],
[-550, 10],
[-530, 10]
]
],
[
[
[530, -10],
[530, -20],
[550, -20],
[550, -10],
[530, -10]
]
]
],

type: 'MultiPolygon'
}
};

const expected: Feature<MultiPolygon> = {
type: 'Feature',
properties: {},
geometry: {
coordinates: [
[
[
[-180, -20],
[-170, -20],
[-170, -10],
[-180, -10],
[-180, -20]
]
],
[
[
[-180, 0],
[-170, 0],
[-170, 10],
[-180, 10],
[-180, 0]
]
],
[
[
[-180, 20],
[-170, 20],
[-170, 30],
[-180, 30],
[-180, 20]
]
],
[
[
[170, -20],
[180, -20],
[180, -10],
[170, -10],
[170, -20]
]
],
[
[
[170, 0],
[180, 0],
[180, 10],
[170, 10],
[170, 0]
]
],
[
[
[170, 20],
[180, 20],
[180, 30],
[170, 30],
[170, 20]
]
]
],
type: 'MultiPolygon'
}
};

const result = fixAntimeridian(feature);
expect(result).toEqual(expected);
});
});
Loading
Loading