Skip to content

Commit

Permalink
fix(cli): mitigate polygon intersection errors
Browse files Browse the repository at this point in the history
Use clipline to reduce polygon when we don't care about degenerate edges. When we do care call
intersection to remove the degenerate edges.

Store image bounds not polygons in SourceMetadata and also store in CogJob. This reduces
floating-point errors when combining source imagery bounds into composite polygons.

Note:

The Martinez polygon-clipping libraries can suffer from errors when lines align. Other intersection
libraries are intractable for the size of coastline polygons.  A solution is to call intersection
after first calling clipline.
  • Loading branch information
Geoff Jacobsen committed Jul 2, 2020
1 parent 4c4e155 commit 7b96808
Show file tree
Hide file tree
Showing 20 changed files with 829 additions and 295 deletions.
1 change: 1 addition & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"@cogeotiff/source-url": "^2.1.1",
"@rushstack/ts-command-line": "^4.3.13",
"ansi-colors": "^4.1.1",
"lineclip": "^1.1.5",
"p-limit": "^3.0.1",
"polygon-clipping": "^0.14.3",
"pretty-json-log": "^0.3.1"
Expand Down
4 changes: 1 addition & 3 deletions packages/cli/src/cli/cogify/action.cog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,6 @@ export class ActionCogCreate extends CommandLineAction {
const tmpFolder = await makeTempFolder(`basemaps-${job.id}-${CliId}`);

try {
const sourceGeo = (await inFp.readJson(getJobPath(job, 'source.geojson'))) as FeatureCollection;

let cutlineJson: FeatureCollection | undefined;
if (job.output.cutline != null) {
const cutlinePath = getJobPath(job, 'cutline.geojson.gz');
Expand All @@ -117,7 +115,7 @@ export class ActionCogCreate extends CommandLineAction {
job.output.cutline?.blend,
);

const tmpVrtPath = await CogVrt.buildVrt(tmpFolder, job, sourceGeo, cutline, name, logger);
const tmpVrtPath = await CogVrt.buildVrt(tmpFolder, job, cutline, name, logger);

if (tmpVrtPath == null) {
logger.warn({ name }, 'NoMatchingSourceImagery');
Expand Down
99 changes: 68 additions & 31 deletions packages/cli/src/cog/__test__/builder.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Epsg, EpsgCode } from '@basemaps/geo';
import { Bounds, Epsg, EpsgCode } from '@basemaps/geo';
import { LogConfig, ProjectionTileMatrixSet } from '@basemaps/shared';
import { round } from '@basemaps/test/build/rounding';
import { CogTiff, TiffTagGeo } from '@cogeotiff/core';
import { CogTiff } from '@cogeotiff/core';
import { CogSourceAwsS3 } from '@cogeotiff/source-aws';
import { CogSourceFile } from '@cogeotiff/source-file';
import o from 'ospec';
import { CogBuilder, guessProjection } from '../builder';

Expand All @@ -28,35 +29,71 @@ o.spec('Builder', () => {

o.spec('tiff', () => {
const googleBuilder = new CogBuilder(ProjectionTileMatrixSet.get(EpsgCode.Google), 1, LogConfig.get());
const tiff = {
source: { name: 'test1.tiff' },
getImage(n: number): any {
if (n != 0) return null;
return {
bbox: [1492000, 6198000, 1492000 + 24000, 6198000 + 36000],
valueGeo(key: number): any {
if (key === TiffTagGeo.ProjectedCSTypeGeoKey) return EpsgCode.Nztm2000;
const origInit = CogTiff.prototype.init;
const origGetImage = CogTiff.prototype.getImage;

o.after(() => {
CogTiff.prototype.init = origInit;
CogTiff.prototype.getImage = origGetImage;
});

o('bounds', async () => {
const localTiff = new CogSourceFile('local/file.tiff');
const s3Tiff = new CogSourceAwsS3('bucket', 's3://file.tiff');

const imageLocal = {
resolution: [0.1],
value: (): any => [1],
valueGeo: (): any => EpsgCode.Nztm2000,
bbox: Bounds.fromJson({
x: 1492000,
y: 6198000,
width: 24000,
height: 36000,
}).toBbox(),
};

const imageS3 = {
resolution: [0.1],
value: (): any => [1],
valueGeo: (): any => EpsgCode.Nztm2000,
bbox: Bounds.fromJson({
x: 1492000 + 24000,
y: 6198000,
width: 24000,
height: 36000,
}).toBbox(),
};

CogTiff.prototype.init = o.spy() as any;
CogTiff.prototype.getImage = function (): any {
return this.source == localTiff ? imageLocal : imageS3;
};

const ans = await googleBuilder.bounds([localTiff, s3Tiff]);

o(ans).deepEquals({
projection: 2193,
nodata: 1,
bands: 1,
bounds: [
{
x: 1492000,
y: 6198000,
width: 24000,
height: 36000,
name: 'local/file.tiff',
},
{
x: 1516000,
y: 6198000,
width: 24000,
height: 36000,
name: 's3://bucket/s3://file.tiff',
},
};
},
} as CogTiff;

o('getTifBounds', () => {
o(round(googleBuilder.getTifBounds(tiff), 2)).deepEquals({
type: 'Feature',
geometry: {
type: 'Polygon',
coordinates: [
[
[171.83, -34.03],
[171.83, -34.35],
[172.09, -34.36],
[172.09, -34.03],
[171.83, -34.03],
],
],
},
properties: { tiff: 'test1.tiff' },
],
pixelScale: 0.1,
resZoom: 21,
});
});
});
Expand Down
Loading

0 comments on commit 7b96808

Please sign in to comment.