Skip to content

Commit 89e515b

Browse files
Merge pull request #29 from sentinel-hub/feature/find-flyover-times-coverage
Feature/find flyovers coverage
2 parents e1254df + f75f8bf commit 89e515b

File tree

10 files changed

+312
-31
lines changed

10 files changed

+312
-31
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -141,17 +141,17 @@ We can always use layer to search for data availability:
141141

142142
const maxCloudCoverPercent = 50;
143143
const layerS2L2A = new S2L2ALayer(instanceId, 'S2L2A', null, null, null, null, null, maxCloudCoverPercent);
144-
const tilesS2L2A = layerS2L2A.findTiles(bbox, fromDate, toDate, maxCount, offset);
145-
const flyoverIntervalsS2L2A = layerS2L2A.findFlyoverIntervals(tilesS2L2A.tiles);
144+
const { tiles, hasMore } = layerS2L2A.findTiles(bbox, fromDate, toDate, maxCount, offset);
145+
const flyoversS2L2A = layerS2L2A.findFlyovers(bbox, fromDate, toDate);
146146
const dates = layerS2L2A.findDatesUTC(bbox, fromDate, toDate);
147147

148148
const layerS1 = new S1GRDAWSEULayer(
149149
instanceId, 'LayerS1GRD',
150150
null, null, null, null, null, null, null, null,
151151
true, BackscatterCoeff.GAMMA0_ELLIPSOID, OrbitDirection.ASCENDING
152152
);
153-
const tilesS1 = layerS1.findTiles(bbox, fromDate, toDate, maxCount, offset);
154-
const flyoverIntervalsS1 = layerS1.findFlyoverIntervals(tilesS1.tiles);
153+
const { tiles: tilesS1 } = layerS1.findTiles(bbox, fromDate, toDate, maxCount, offset);
154+
const flyoversS1 = layerS1.findFlyovers(bbox, fromDate, toDate);
155155
```
156156

157157
## Backwards compatibility

example/node/index.js

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -110,30 +110,44 @@ async function run() {
110110

111111
// get tiles and flyover intervals for S2 L2A layer
112112
const layerS2L2A = new S2L2ALayer(instanceId, s2l2aLayerId);
113-
const tilesS2L2A = await layerS2L2A.findTiles(
113+
const { tiles: tilesS2L2A, hasMore } = await layerS2L2A.findTiles(
114114
getMapParams.bbox,
115115
getMapParams.fromTime,
116116
getMapParams.toTime,
117117
20,
118118
0,
119-
100,
120119
);
121120
printOut('tiles for S2 L2A:', tilesS2L2A);
122-
const flyoverIntervalsS2L2A = layerS2L2A.findFlyoverIntervals(tilesS2L2A.tiles);
123-
printOut('flyover intervals for S2 L2A:', flyoverIntervalsS2L2A);
121+
printOut('hasMore:', hasMore);
122+
123+
const flyoversS2L2A = await layerS2L2A.findFlyovers(
124+
getMapParams.bbox,
125+
getMapParams.fromTime,
126+
getMapParams.toTime,
127+
10,
128+
50,
129+
);
130+
printOut('flyovers for S2 L2A:', flyoversS2L2A);
124131

125132
// get tiles and flyover intervals for S1 GRD Layer
126-
const tilesS1GRD = await layerS1.findTiles(
133+
const { tiles: tilesS1GRD, hasMore: hasMoreS1 } = await layerS1.findTiles(
127134
getMapParams.bbox,
128135
getMapParams.fromTime,
129136
getMapParams.toTime,
130137
10,
131138
0,
132-
OrbitDirection.ASCENDING,
133139
);
134140
printOut('tiles for S1 GRD:', tilesS1GRD);
135-
const flyoverIntervalsS1GRD = layerS1.findFlyoverIntervals(tilesS1GRD.tiles);
136-
printOut('flyover intervals for S1 GRD:', flyoverIntervalsS1GRD);
141+
printOut('hasMore for S1:', hasMoreS1);
142+
143+
const flyoversS1GRD = await layerS1.findFlyovers(
144+
getMapParams.bbox,
145+
getMapParams.fromTime,
146+
getMapParams.toTime,
147+
10,
148+
50,
149+
);
150+
printOut('flyovers for S1 GRD:', flyoversS1GRD);
137151

138152
// finally, display the image:
139153
const imageUrl = await layerS2L2A.getMapUrl(getMapParams, ApiType.WMS);

package-lock.json

Lines changed: 75 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
"module": "dist/sentinelHub.esm.js",
66
"browser": "dist/sentinelHub.umd.js",
77
"dependencies": {
8+
"@turf/area": "^6.0.1",
89
"@turf/helpers": "^6.1.4",
10+
"@turf/intersect": "^6.1.3",
11+
"@turf/union": "^5.1.5",
912
"@types/xml2js": "^0.4.4",
1013
"axios": "^0.18.1",
1114
"query-string": "^6.4.2",
@@ -14,9 +17,9 @@
1417
"devDependencies": {
1518
"@babel/core": "^7.8.3",
1619
"@storybook/html": "^5.3.3",
20+
"@types/jest": "^25.1.3",
1721
"@typescript-eslint/eslint-plugin": "^1.9.0",
1822
"@typescript-eslint/parser": "^1.9.0",
19-
"@types/jest": "^25.1.2",
2023
"babel-loader": "^8.0.6",
2124
"dotenv": "^8.2.0",
2225
"eslint": "^5.16.0",
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
declare module '@turf/union' {
2+
import { MultiPolygon, Polygon } from '@turf/helpers';
3+
export default function union(
4+
poly1: Polygon | MultiPolygon,
5+
poly2: Polygon | MultiPolygon,
6+
): Polygon | MultiPolygon;
7+
}

src/layer/AbstractLayer.ts

Lines changed: 83 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ import { GetMapParams, ApiType, Tile, PaginatedTiles, FlyoverInterval } from 'sr
44
import { BBox } from 'src/bbox';
55
import { Dataset } from 'src/layer/dataset';
66
import { RequestConfig } from 'src/utils/axiosInterceptors';
7+
import intersect from '@turf/intersect';
8+
import area from '@turf/area';
9+
import union from '@turf/union'; // @turf/union is missing types definitions, we supply them separately
10+
11+
import { Polygon, MultiPolygon } from '@turf/helpers';
12+
import { CRS_EPSG4326 } from 'src/crs';
713

814
export class AbstractLayer {
915
public title: string | null = null;
@@ -44,41 +50,104 @@ export class AbstractLayer {
4450
throw new Error('Not implemented yet');
4551
}
4652

47-
public findFlyoverIntervals(tiles: Tile[]): FlyoverInterval[] {
53+
public async findFlyovers(
54+
bbox: BBox,
55+
fromTime: Date,
56+
toTime: Date,
57+
maxFindTilesRequests: number = 50,
58+
tilesPerRequest: number = 50,
59+
): Promise<FlyoverInterval[]> {
4860
if (!this.dataset || !this.dataset.orbitTimeMinutes) {
4961
throw new Error('Orbit time is needed for grouping tiles into flyovers.');
5062
}
5163

64+
if (bbox.crs !== CRS_EPSG4326) {
65+
throw new Error('Currently, only EPSG:4326 in findFlyovers');
66+
}
67+
68+
let tiles: Tile[] = [];
69+
for (let i = 0; i < maxFindTilesRequests; i++) {
70+
const { tiles: partialTiles, hasMore } = await this.findTiles(
71+
bbox,
72+
fromTime,
73+
toTime,
74+
tilesPerRequest,
75+
i * tilesPerRequest,
76+
);
77+
tiles = tiles.concat(partialTiles);
78+
if (!hasMore) {
79+
break;
80+
}
81+
if (i + 1 === maxFindTilesRequests) {
82+
throw new Error(
83+
`Could not fetch all the tiles in [${maxFindTilesRequests}] requests for [${tilesPerRequest}] tiles`,
84+
);
85+
}
86+
}
87+
5288
tiles.sort((a, b) => a.sensingTime.getTime() - b.sensingTime.getTime());
5389
let orbitTimeMS = this.dataset.orbitTimeMinutes * 60 * 1000;
5490
let flyoverIntervals: FlyoverInterval[] = [];
5591

56-
let j = 0;
57-
for (let i = 0; i < tiles.length; i++) {
58-
if (i === 0) {
59-
flyoverIntervals[j] = {
60-
fromTime: tiles[i].sensingTime,
61-
toTime: tiles[i].sensingTime,
92+
let flyoverIndex = 0;
93+
let currentFlyoverGeometry = null;
94+
for (let tileIndex = 0; tileIndex < tiles.length; tileIndex++) {
95+
if (tileIndex === 0) {
96+
flyoverIntervals[flyoverIndex] = {
97+
fromTime: tiles[tileIndex].sensingTime,
98+
toTime: tiles[tileIndex].sensingTime,
99+
coveragePercent: 0,
62100
};
101+
currentFlyoverGeometry = tiles[tileIndex].geometry;
63102
continue;
64103
}
65104

66-
const prevDateMS = tiles[i - 1].sensingTime.getTime();
67-
const currDateMS = tiles[i].sensingTime.getTime();
105+
const prevDateMS = tiles[tileIndex - 1].sensingTime.getTime();
106+
const currDateMS = tiles[tileIndex].sensingTime.getTime();
68107
const diffMS = Math.abs(prevDateMS - currDateMS);
69108

70109
if (diffMS < orbitTimeMS) {
71-
flyoverIntervals[j].toTime = tiles[i].sensingTime;
110+
flyoverIntervals[flyoverIndex].toTime = tiles[tileIndex].sensingTime;
111+
currentFlyoverGeometry = union(currentFlyoverGeometry, tiles[tileIndex].geometry);
72112
} else {
73-
j++;
74-
flyoverIntervals[j] = {
75-
fromTime: tiles[i].sensingTime,
76-
toTime: tiles[i].sensingTime,
113+
flyoverIntervals[flyoverIndex].coveragePercent = this.calculateCoveragePercent(
114+
bbox,
115+
currentFlyoverGeometry,
116+
);
117+
flyoverIndex++;
118+
flyoverIntervals[flyoverIndex] = {
119+
fromTime: tiles[tileIndex].sensingTime,
120+
toTime: tiles[tileIndex].sensingTime,
121+
coveragePercent: 0,
77122
};
123+
currentFlyoverGeometry = tiles[tileIndex].geometry;
78124
}
79125
}
126+
if (flyoverIntervals.length > 0) {
127+
flyoverIntervals[flyoverIndex].coveragePercent = this.calculateCoveragePercent(
128+
bbox,
129+
currentFlyoverGeometry,
130+
);
131+
}
80132
return flyoverIntervals;
81133
}
82134

135+
private calculateCoveragePercent(bbox: BBox, flyoverGeometry: Polygon | MultiPolygon): number {
136+
const bboxGeometry: Polygon = {
137+
type: 'Polygon',
138+
coordinates: [
139+
[
140+
[bbox.minX, bbox.minY],
141+
[bbox.maxX, bbox.minY],
142+
[bbox.maxX, bbox.maxY],
143+
[bbox.minX, bbox.maxY],
144+
[bbox.minX, bbox.minY],
145+
],
146+
],
147+
};
148+
const bboxedFlyoverGeometry = intersect(bboxGeometry, flyoverGeometry);
149+
return (area(bboxedFlyoverGeometry) / area(bboxGeometry)) * 100;
150+
}
151+
83152
public async updateLayerFromServiceIfNeeded(): Promise<void> {}
84153
}

src/layer/const.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export type PaginatedTiles = {
6262
export type FlyoverInterval = {
6363
fromTime: Date;
6464
toTime: Date;
65+
coveragePercent: number;
6566
};
6667

6768
export type MimeType =

0 commit comments

Comments
 (0)