mapshaper 0.7.58 → 0.7.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/mapshaper.js +182 -55
- package/package.json +1 -1
- package/www/basemap.js +1 -1
- package/www/mapshaper-gui.js +38 -3
- package/www/mapshaper.js +182 -55
package/mapshaper.js
CHANGED
|
@@ -18168,10 +18168,29 @@
|
|
|
18168
18168
|
parseSizeParam: parseSizeParam
|
|
18169
18169
|
});
|
|
18170
18170
|
|
|
18171
|
-
//
|
|
18172
|
-
// length.
|
|
18171
|
+
// Conservative width as a fraction of the layer's median polygon-ring segment
|
|
18172
|
+
// length. Used for partition min-width and duplicate-boundary search — not for
|
|
18173
|
+
// the default gap-width=auto fill threshold (see getDefaultGapWidth).
|
|
18173
18174
|
var GAP_WIDTH_SEGMENT_FRACTION = 0.01;
|
|
18174
18175
|
|
|
18176
|
+
// Inject gap_width=auto unless the caller already chose a width or a legacy
|
|
18177
|
+
// area/sliver option. Used by -clean, -filter-slivers, and gap partition.
|
|
18178
|
+
function applyDefaultGapWidthOpts(opts) {
|
|
18179
|
+
opts = Object.assign({}, opts);
|
|
18180
|
+
if (opts.gap_width != null) return opts;
|
|
18181
|
+
// Legacy area/sliver options: keep the historical gap_fill_area=auto default.
|
|
18182
|
+
if (opts.gap_fill_area != null || opts.min_gap_area != null ||
|
|
18183
|
+
opts.min_area != null || opts.sliver_control != null) {
|
|
18184
|
+
if (opts.gap_fill_area == null && opts.min_gap_area == null &&
|
|
18185
|
+
opts.min_area == null) {
|
|
18186
|
+
opts.gap_fill_area = 'auto';
|
|
18187
|
+
}
|
|
18188
|
+
return opts;
|
|
18189
|
+
}
|
|
18190
|
+
opts.gap_width = 'auto';
|
|
18191
|
+
return opts;
|
|
18192
|
+
}
|
|
18193
|
+
|
|
18175
18194
|
// Used by -clean -dissolve -filter-slivers -filter-islands to generate filters
|
|
18176
18195
|
// for removing small polygon rings / filling mosaic gaps.
|
|
18177
18196
|
// Prefers gap_width= (including 'auto'). Legacy gap_fill_area / sliver_control
|
|
@@ -18317,9 +18336,10 @@
|
|
|
18317
18336
|
};
|
|
18318
18337
|
}
|
|
18319
18338
|
|
|
18320
|
-
//
|
|
18339
|
+
// Width equivalent of the legacy gap-fill-area=auto + sliver-control=1 test:
|
|
18340
|
+
// W = sqrt(A0 / π). The two filters accept the same rings (see getGapWidthTest).
|
|
18321
18341
|
function getDefaultGapWidth(lyr, arcs) {
|
|
18322
|
-
return
|
|
18342
|
+
return Math.sqrt(getDefaultSliverThreshold(lyr, arcs) / Math.PI);
|
|
18323
18343
|
}
|
|
18324
18344
|
|
|
18325
18345
|
function getMedianPolygonSegmentLength(lyr, arcs) {
|
|
@@ -18400,6 +18420,7 @@
|
|
|
18400
18420
|
var Slivers = /*#__PURE__*/Object.freeze({
|
|
18401
18421
|
__proto__: null,
|
|
18402
18422
|
GAP_WIDTH_SEGMENT_FRACTION: GAP_WIDTH_SEGMENT_FRACTION,
|
|
18423
|
+
applyDefaultGapWidthOpts: applyDefaultGapWidthOpts,
|
|
18403
18424
|
calcMaxSliverArea: calcMaxSliverArea,
|
|
18404
18425
|
getDefaultGapWidth: getDefaultGapWidth,
|
|
18405
18426
|
getDefaultSliverThreshold: getDefaultSliverThreshold,
|
|
@@ -23467,8 +23488,7 @@
|
|
|
23467
23488
|
var spherical = isLatLngCRS(crs);
|
|
23468
23489
|
var medianSeg = measureMedianSegment(lyr, arcs);
|
|
23469
23490
|
// Same width as interior gap filling, from the same automatic default.
|
|
23470
|
-
var distance = resolveCloseDistance(opts, crs,
|
|
23471
|
-
medianSeg * GAP_WIDTH_SEGMENT_FRACTION);
|
|
23491
|
+
var distance = resolveCloseDistance(opts, crs, getDefaultGapWidth(lyr, arcs));
|
|
23472
23492
|
if (!(distance > 0)) return 0;
|
|
23473
23493
|
|
|
23474
23494
|
profileStart('cg.outsideFacingArcs');
|
|
@@ -23518,7 +23538,7 @@
|
|
|
23518
23538
|
return {seams: seams, seeds: seeds, arcsById: arcsById, footpoints: footpoints};
|
|
23519
23539
|
}
|
|
23520
23540
|
|
|
23521
|
-
//
|
|
23541
|
+
// Median of every polygon-ring segment; used for min-seam length.
|
|
23522
23542
|
function measureMedianSegment(lyr, arcs) {
|
|
23523
23543
|
return profileWrap('cg.medianSegment', function() {
|
|
23524
23544
|
return getMedianPolygonSegmentLength(lyr, arcs);
|
|
@@ -24154,18 +24174,7 @@
|
|
|
24154
24174
|
function partitionPolygonMosaicGaps(lyr, dataset, nodes, opts) {
|
|
24155
24175
|
if (!lyr.shapes || lyr.shapes.length < MIN_GAP_OWNERS) return false;
|
|
24156
24176
|
var mosaicIndex = new MosaicIndex(lyr, nodes, {flat: true});
|
|
24157
|
-
var sliverOpts =
|
|
24158
|
-
if (sliverOpts.gap_width == null) {
|
|
24159
|
-
if (sliverOpts.gap_fill_area != null || sliverOpts.min_gap_area != null ||
|
|
24160
|
-
sliverOpts.min_area != null || sliverOpts.sliver_control != null) {
|
|
24161
|
-
if (sliverOpts.gap_fill_area == null && sliverOpts.min_gap_area == null &&
|
|
24162
|
-
sliverOpts.min_area == null) {
|
|
24163
|
-
sliverOpts.gap_fill_area = 'auto';
|
|
24164
|
-
}
|
|
24165
|
-
} else {
|
|
24166
|
-
sliverOpts.gap_width = 'auto';
|
|
24167
|
-
}
|
|
24168
|
-
}
|
|
24177
|
+
var sliverOpts = applyDefaultGapWidthOpts(opts);
|
|
24169
24178
|
var filter = getSliverFilter(lyr, dataset, sliverOpts).filter;
|
|
24170
24179
|
var gaps = mosaicIndex.getUnusedTileData(filter).filter(function(gap) {
|
|
24171
24180
|
return countOwners(gap.boundary) >= MIN_GAP_OWNERS;
|
|
@@ -24601,10 +24610,11 @@
|
|
|
24601
24610
|
// several features with hairline slivers, which is worse for having more of them.
|
|
24602
24611
|
//
|
|
24603
24612
|
// A layer's median segment length is the scale at which its boundaries were
|
|
24604
|
-
// drawn
|
|
24605
|
-
//
|
|
24606
|
-
//
|
|
24607
|
-
// effect: raising that to fill wider gaps should not coarsen
|
|
24613
|
+
// drawn. A small fraction of that scale marks where dividing a gap stops being
|
|
24614
|
+
// worth it: the displacement would be smaller than the data's own detail, and
|
|
24615
|
+
// the pieces would be hairline slivers. This is deliberately independent of
|
|
24616
|
+
// the gap-width in effect: raising that to fill wider gaps should not coarsen
|
|
24617
|
+
// the data's own scale.
|
|
24608
24618
|
function getMinDivisibleGapWidth(lyr, arcs) {
|
|
24609
24619
|
var width = getMedianPolygonSegmentLength(lyr, arcs) *
|
|
24610
24620
|
GAP_WIDTH_SEGMENT_FRACTION;
|
|
@@ -24741,7 +24751,7 @@
|
|
|
24741
24751
|
|
|
24742
24752
|
function cleanPolygonLayerGeometry(lyr, dataset, opts) {
|
|
24743
24753
|
// clean polygons by apply the 'dissolve2' function to each feature
|
|
24744
|
-
opts =
|
|
24754
|
+
opts = applyDefaultGapWidthOpts(opts);
|
|
24745
24755
|
var groups = lyr.shapes.map(function(shp, i) {
|
|
24746
24756
|
return [i];
|
|
24747
24757
|
});
|
|
@@ -24750,22 +24760,6 @@
|
|
|
24750
24760
|
markLayerChanged(lyr, {operation: 'cleanPolygonLayerGeometry', unit: 'shapes'});
|
|
24751
24761
|
}
|
|
24752
24762
|
|
|
24753
|
-
function withDefaultGapWidth(opts) {
|
|
24754
|
-
opts = Object.assign({}, opts);
|
|
24755
|
-
if (opts.gap_width != null) return opts;
|
|
24756
|
-
// Legacy area/sliver options: keep the historical gap_fill_area=auto default.
|
|
24757
|
-
if (opts.gap_fill_area != null || opts.min_gap_area != null ||
|
|
24758
|
-
opts.min_area != null || opts.sliver_control != null) {
|
|
24759
|
-
if (opts.gap_fill_area == null && opts.min_gap_area == null &&
|
|
24760
|
-
opts.min_area == null) {
|
|
24761
|
-
opts.gap_fill_area = 'auto';
|
|
24762
|
-
}
|
|
24763
|
-
return opts;
|
|
24764
|
-
}
|
|
24765
|
-
opts.gap_width = 'auto';
|
|
24766
|
-
return opts;
|
|
24767
|
-
}
|
|
24768
|
-
|
|
24769
24763
|
function collectArcIds(shapes) {
|
|
24770
24764
|
var ids = {};
|
|
24771
24765
|
traversePaths(shapes, function(o) {
|
|
@@ -40329,12 +40323,13 @@ ${svg}
|
|
|
40329
40323
|
|
|
40330
40324
|
parser.command('filter-slivers')
|
|
40331
40325
|
.describe('remove small polygon rings')
|
|
40326
|
+
.option('gap-width', gapWidthOpt)
|
|
40332
40327
|
.option('min-area', {
|
|
40333
40328
|
type: 'area',
|
|
40334
|
-
describe: '
|
|
40329
|
+
describe: '(deprecated) use gap-width= instead'
|
|
40335
40330
|
})
|
|
40336
40331
|
.option('sliver-control', {
|
|
40337
|
-
describe: '
|
|
40332
|
+
describe: '(deprecated) use gap-width= instead',
|
|
40338
40333
|
type: 'number'
|
|
40339
40334
|
})
|
|
40340
40335
|
.option('weighted', {
|
|
@@ -40383,9 +40378,9 @@ ${svg}
|
|
|
40383
40378
|
.option('no-replace', noReplaceOpt);
|
|
40384
40379
|
|
|
40385
40380
|
parser.command('grid')
|
|
40386
|
-
.describe('create a grid of square, hexagonal, rhombus or
|
|
40381
|
+
.describe('create a grid of square, hexagonal, rhombus, triangle or cairo polygons')
|
|
40387
40382
|
.option('type', {
|
|
40388
|
-
describe: 'square, square2, hex, hex2, rhombus, rhombus2, triangle or
|
|
40383
|
+
describe: 'square, square2, hex, hex2, rhombus, rhombus2, triangle, triangle2 or cairo (default is square)'
|
|
40389
40384
|
})
|
|
40390
40385
|
.option('interval', {
|
|
40391
40386
|
describe: 'side length (e.g. 500m, 12km)',
|
|
@@ -40407,6 +40402,10 @@ ${svg}
|
|
|
40407
40402
|
describe: 'scale factor for cells, between 0 and 2',
|
|
40408
40403
|
type: 'number'
|
|
40409
40404
|
})
|
|
40405
|
+
.option('rotate', {
|
|
40406
|
+
describe: 'rotation angle in degrees (default is 0)',
|
|
40407
|
+
type: 'number'
|
|
40408
|
+
})
|
|
40410
40409
|
// .option('bbox', {
|
|
40411
40410
|
// type: 'bbox',
|
|
40412
40411
|
// describe: 'xmin,ymin,xmax,ymax (default is bbox of data)'
|
|
@@ -56569,7 +56568,13 @@ ${svg}
|
|
|
56569
56568
|
};
|
|
56570
56569
|
|
|
56571
56570
|
function filterSlivers(lyr, dataset, optsArg) {
|
|
56572
|
-
var opts =
|
|
56571
|
+
var opts = applyDefaultGapWidthOpts(optsArg);
|
|
56572
|
+
// Legacy area path only: keep the historical sliver-control=1 default.
|
|
56573
|
+
// Do not inject it before applyDefaultGapWidthOpts, or the implicit default
|
|
56574
|
+
// would lock the command onto the area path.
|
|
56575
|
+
if (opts.gap_width == null && opts.sliver_control == null) {
|
|
56576
|
+
opts.sliver_control = 1;
|
|
56577
|
+
}
|
|
56573
56578
|
var filterData = getSliverFilter(lyr, dataset, opts);
|
|
56574
56579
|
var ringTest = filterData.filter;
|
|
56575
56580
|
var removed = 0;
|
|
@@ -74555,7 +74560,7 @@ ${svg}
|
|
|
74555
74560
|
cmd.polygonGrid = function(targetLayers, targetDataset, opts) {
|
|
74556
74561
|
requireProjectedDataset(targetDataset);
|
|
74557
74562
|
var params = getGridParams(targetLayers, targetDataset, opts);
|
|
74558
|
-
var gridDataset = makeGridDataset(params); // grid is a new dataset
|
|
74563
|
+
var gridDataset = makeGridDataset(params, opts); // grid is a new dataset
|
|
74559
74564
|
gridDataset.info = copyDatasetInfo(targetDataset.info);
|
|
74560
74565
|
setOutputLayerName(gridDataset.layers[0], null, 'grid', opts);
|
|
74561
74566
|
if (opts.debug) gridDataset.layers.push(cmd.pointGrid2(targetLayers, targetDataset, opts));
|
|
@@ -74619,35 +74624,67 @@ ${svg}
|
|
|
74619
74624
|
|
|
74620
74625
|
function makeGridDataset(params, opts) {
|
|
74621
74626
|
var geojson, dataset;
|
|
74627
|
+
var rotation = opts.rotate || 0;
|
|
74628
|
+
var genParams = rotation ? getRotatedGridParams(params, rotation) : params;
|
|
74622
74629
|
if (params.type == 'square') {
|
|
74623
|
-
geojson = getSquareGridGeoJSON(getSquareGridCoordinates(
|
|
74630
|
+
geojson = getSquareGridGeoJSON(getSquareGridCoordinates(genParams));
|
|
74624
74631
|
} else if (params.type == 'square2') {
|
|
74625
|
-
geojson = getRotatedSquareGridGeoJSON(
|
|
74632
|
+
geojson = getRotatedSquareGridGeoJSON(genParams);
|
|
74626
74633
|
} else if (params.type == 'hex') {
|
|
74627
|
-
geojson = getHexGridGeoJSON(getHexGridCoordinates(
|
|
74634
|
+
geojson = getHexGridGeoJSON(getHexGridCoordinates(genParams));
|
|
74628
74635
|
} else if (params.type == 'hex2') {
|
|
74629
74636
|
// use rotated grid
|
|
74630
|
-
geojson = getHexGridGeoJSON(getHexGridCoordinates(swapGridParams(
|
|
74637
|
+
geojson = getHexGridGeoJSON(getHexGridCoordinates(swapGridParams(genParams)));
|
|
74631
74638
|
swapPolygonCoords(geojson);
|
|
74632
74639
|
} else if (params.type == 'rhombus') {
|
|
74633
|
-
geojson = getRhombusGridGeoJSON(
|
|
74640
|
+
geojson = getRhombusGridGeoJSON(genParams, false);
|
|
74634
74641
|
} else if (params.type == 'rhombus2') {
|
|
74635
|
-
geojson = getRhombusGridGeoJSON(
|
|
74642
|
+
geojson = getRhombusGridGeoJSON(genParams, true);
|
|
74636
74643
|
} else if (params.type == 'triangle') {
|
|
74637
|
-
geojson = getTriangleGridGeoJSON(
|
|
74644
|
+
geojson = getTriangleGridGeoJSON(genParams, false);
|
|
74638
74645
|
} else if (params.type == 'triangle2') {
|
|
74639
|
-
geojson = getTriangleGridGeoJSON(
|
|
74646
|
+
geojson = getTriangleGridGeoJSON(genParams, true);
|
|
74647
|
+
} else if (params.type == 'cairo') {
|
|
74648
|
+
geojson = getCairoGridGeoJSON(genParams);
|
|
74640
74649
|
} else {
|
|
74641
74650
|
stop$1('Unsupported grid type');
|
|
74642
74651
|
}
|
|
74643
74652
|
scaleGridCells(geojson, params.cellScale);
|
|
74644
74653
|
alignGridToBounds(geojson, params.bbox);
|
|
74654
|
+
if (rotation) {
|
|
74655
|
+
rotateGridCells(geojson, rotation, params.bbox);
|
|
74656
|
+
}
|
|
74645
74657
|
cullGridCells(geojson, params.bbox);
|
|
74646
74658
|
dataset = importGeoJSON(geojson, {});
|
|
74647
74659
|
buildTopology(dataset);
|
|
74648
74660
|
return dataset;
|
|
74649
74661
|
}
|
|
74650
74662
|
|
|
74663
|
+
// Expand the generation rectangle so that, after rotation around the shared
|
|
74664
|
+
// center, the grid still covers the original target bbox.
|
|
74665
|
+
function getRotatedGridParams(params, rotation) {
|
|
74666
|
+
var angle = rotation * Math.PI / 180;
|
|
74667
|
+
var cos = Math.abs(Math.cos(angle));
|
|
74668
|
+
var sin = Math.abs(Math.sin(angle));
|
|
74669
|
+
return utils.defaults({
|
|
74670
|
+
width: params.width * cos + params.height * sin,
|
|
74671
|
+
height: params.width * sin + params.height * cos
|
|
74672
|
+
}, params);
|
|
74673
|
+
}
|
|
74674
|
+
|
|
74675
|
+
function rotateGridCells(geojson, rotation, bbox) {
|
|
74676
|
+
var cx = (bbox[0] + bbox[2]) / 2;
|
|
74677
|
+
var cy = (bbox[1] + bbox[3]) / 2;
|
|
74678
|
+
var transform = getAffineTransform(rotation, 1, [0, 0], [cx, cy]);
|
|
74679
|
+
geojson.geometries.forEach(function(geom) {
|
|
74680
|
+
if (geom.type == 'Polygon') {
|
|
74681
|
+
geom.coordinates[0] = geom.coordinates[0].map(function(xy) {
|
|
74682
|
+
return transform(xy[0], xy[1]);
|
|
74683
|
+
});
|
|
74684
|
+
}
|
|
74685
|
+
});
|
|
74686
|
+
}
|
|
74687
|
+
|
|
74651
74688
|
function swapGridParams(params) {
|
|
74652
74689
|
var bbox = params.bbox;
|
|
74653
74690
|
return utils.defaults({
|
|
@@ -74729,6 +74766,7 @@ ${svg}
|
|
|
74729
74766
|
if (type == 'hex' || type == 'hex2') return 3 * Math.sqrt(3) / 2;
|
|
74730
74767
|
if (type == 'rhombus' || type == 'rhombus2') return Math.sqrt(3) / 2;
|
|
74731
74768
|
if (type == 'triangle' || type == 'triangle2') return Math.sqrt(3) / 4;
|
|
74769
|
+
if (type == 'cairo') return (4 + Math.sqrt(7)) / 4;
|
|
74732
74770
|
stop$1('Unsupported grid type');
|
|
74733
74771
|
}
|
|
74734
74772
|
|
|
@@ -74882,6 +74920,95 @@ ${svg}
|
|
|
74882
74920
|
return geojson;
|
|
74883
74921
|
}
|
|
74884
74922
|
|
|
74923
|
+
// Equilateral type-4 Cairo tessellation. Four-valent (90°) vertices sit on a
|
|
74924
|
+
// square lattice of spacing S = interval * (√7+1)/2; each lattice square
|
|
74925
|
+
// contains a central bar of length `interval`, alternating horizontal and
|
|
74926
|
+
// vertical. Two pentagons share each bar.
|
|
74927
|
+
//
|
|
74928
|
+
// Vertices are computed once and reused. Algebraically equal expressions
|
|
74929
|
+
// such as i*S and (i-1)*S + S are not bit-identical, and independently
|
|
74930
|
+
// computed copies of the same 4-way junction become T-intersections.
|
|
74931
|
+
function getCairoGridGeoJSON(params) {
|
|
74932
|
+
var side = params.interval;
|
|
74933
|
+
var spacing = side * (Math.sqrt(7) + 1) / 2;
|
|
74934
|
+
var halfBar = side / 2;
|
|
74935
|
+
var iMin = -1;
|
|
74936
|
+
var jMin = -1;
|
|
74937
|
+
var iMax = Math.ceil(params.width / spacing) + 1;
|
|
74938
|
+
var jMax = Math.ceil(params.height / spacing) + 1;
|
|
74939
|
+
var lattice = [];
|
|
74940
|
+
var bars = [];
|
|
74941
|
+
var geometries = [];
|
|
74942
|
+
var i, j, y, cx, cy;
|
|
74943
|
+
|
|
74944
|
+
for (j = jMin - 1; j <= jMax + 1; j++) {
|
|
74945
|
+
y = j * spacing;
|
|
74946
|
+
lattice[j] = [];
|
|
74947
|
+
for (i = iMin - 1; i <= iMax + 1; i++) {
|
|
74948
|
+
lattice[j][i] = [i * spacing, y];
|
|
74949
|
+
}
|
|
74950
|
+
}
|
|
74951
|
+
|
|
74952
|
+
for (j = jMin - 1; j < jMax + 1; j++) {
|
|
74953
|
+
bars[j] = [];
|
|
74954
|
+
for (i = iMin - 1; i < iMax + 1; i++) {
|
|
74955
|
+
cx = (i + 0.5) * spacing;
|
|
74956
|
+
cy = (j + 0.5) * spacing;
|
|
74957
|
+
bars[j][i] = (i + j) % 2 === 0 ? {
|
|
74958
|
+
a: [cx - halfBar, cy],
|
|
74959
|
+
b: [cx + halfBar, cy]
|
|
74960
|
+
} : {
|
|
74961
|
+
a: [cx, cy - halfBar],
|
|
74962
|
+
b: [cx, cy + halfBar]
|
|
74963
|
+
};
|
|
74964
|
+
}
|
|
74965
|
+
}
|
|
74966
|
+
|
|
74967
|
+
for (j = jMin; j < jMax; j++) {
|
|
74968
|
+
for (i = iMin; i < iMax; i++) {
|
|
74969
|
+
if ((i + j) % 2 === 0) {
|
|
74970
|
+
appendCairoPentagon(geometries, [
|
|
74971
|
+
lattice[j][i],
|
|
74972
|
+
bars[j - 1][i].b,
|
|
74973
|
+
lattice[j][i + 1],
|
|
74974
|
+
bars[j][i].b,
|
|
74975
|
+
bars[j][i].a
|
|
74976
|
+
]);
|
|
74977
|
+
appendCairoPentagon(geometries, [
|
|
74978
|
+
lattice[j + 1][i],
|
|
74979
|
+
bars[j][i].a,
|
|
74980
|
+
bars[j][i].b,
|
|
74981
|
+
lattice[j + 1][i + 1],
|
|
74982
|
+
bars[j + 1][i].a
|
|
74983
|
+
]);
|
|
74984
|
+
} else {
|
|
74985
|
+
appendCairoPentagon(geometries, [
|
|
74986
|
+
lattice[j][i],
|
|
74987
|
+
bars[j][i].a,
|
|
74988
|
+
bars[j][i].b,
|
|
74989
|
+
lattice[j + 1][i],
|
|
74990
|
+
bars[j][i - 1].b
|
|
74991
|
+
]);
|
|
74992
|
+
appendCairoPentagon(geometries, [
|
|
74993
|
+
lattice[j][i + 1],
|
|
74994
|
+
bars[j][i + 1].a,
|
|
74995
|
+
lattice[j + 1][i + 1],
|
|
74996
|
+
bars[j][i].b,
|
|
74997
|
+
bars[j][i].a
|
|
74998
|
+
]);
|
|
74999
|
+
}
|
|
75000
|
+
}
|
|
75001
|
+
}
|
|
75002
|
+
return {type: 'GeometryCollection', geometries: geometries};
|
|
75003
|
+
}
|
|
75004
|
+
|
|
75005
|
+
function appendCairoPentagon(geometries, verts) {
|
|
75006
|
+
geometries.push({
|
|
75007
|
+
type: 'Polygon',
|
|
75008
|
+
coordinates: [verts.concat([verts[0]])]
|
|
75009
|
+
});
|
|
75010
|
+
}
|
|
75011
|
+
|
|
74885
75012
|
function subdivideHexagon(coords) {
|
|
74886
75013
|
var center = getPolygonRingCenter(coords);
|
|
74887
75014
|
return [
|
|
@@ -80084,7 +80211,7 @@ ${svg}
|
|
|
80084
80211
|
return name == 'rectangle' || name == 'rectangles' || name == 'filter' && opts.cleanup;
|
|
80085
80212
|
}
|
|
80086
80213
|
|
|
80087
|
-
var version = "0.7.
|
|
80214
|
+
var version = "0.7.60";
|
|
80088
80215
|
|
|
80089
80216
|
// Parse command line args into commands and run them
|
|
80090
80217
|
// Function takes an optional Node-style callback. A Promise is returned if no callback is given.
|
package/package.json
CHANGED
package/www/basemap.js
CHANGED
|
@@ -3,7 +3,7 @@ window.mapboxParams = {
|
|
|
3
3
|
// css: 'https://api.mapbox.com/mapbox-gl-js/v2.11.1/mapbox-gl.css',
|
|
4
4
|
js: 'https://api.mapbox.com/mapbox-gl-js/v3.15.0/mapbox-gl.js',
|
|
5
5
|
css: 'https://api.mapbox.com/mapbox-gl-js/v3.15.0/mapbox-gl.css',
|
|
6
|
-
production_key: 'pk.
|
|
6
|
+
production_key: 'pk.eyJ1IjoiZ3JhbW1hdGEiLCJhIjoiY210cjg2NTk5MDBiMTJ4b3dvbDNwMjdudSJ9.EohCQlad59ntK8CqWlopKw',
|
|
7
7
|
localhost_key: 'pk.eyJ1IjoiZ3JhbW1hdGEiLCJhIjoiY21jZ2Rna2hrMGo2ZjJqcHFxMHAycDlyMSJ9.q8FchhB-NAN_FGBHN0AJeg',
|
|
8
8
|
styles: [{
|
|
9
9
|
name: 'Reference map',
|
package/www/mapshaper-gui.js
CHANGED
|
@@ -11109,6 +11109,8 @@
|
|
|
11109
11109
|
|
|
11110
11110
|
var openMenu;
|
|
11111
11111
|
var openMenuId;
|
|
11112
|
+
// One menu element per parent element, created on demand.
|
|
11113
|
+
var menus = new WeakMap();
|
|
11112
11114
|
// Prefixes shown to the left of a menu item. The checkmark is what a copyable
|
|
11113
11115
|
// item shows after it has been copied.
|
|
11114
11116
|
var BULLET = '• ';
|
|
@@ -11132,14 +11134,26 @@
|
|
|
11132
11134
|
}
|
|
11133
11135
|
}
|
|
11134
11136
|
|
|
11137
|
+
// Menus are re-used, rather than created per click: a discarded menu stays in
|
|
11138
|
+
// the DOM, and an empty one is a 10px sliver at the bottom of the page, which
|
|
11139
|
+
// is enough to make the page scrollable (see the note in ContextMenu()).
|
|
11140
|
+
function getContextMenu(parentArg) {
|
|
11141
|
+
var parent = parentArg || document.querySelector('body');
|
|
11142
|
+
var menu = menus.get(parent);
|
|
11143
|
+
if (!menu) {
|
|
11144
|
+
menu = new ContextMenu(parent);
|
|
11145
|
+
menus.set(parent, menu);
|
|
11146
|
+
}
|
|
11147
|
+
return menu;
|
|
11148
|
+
}
|
|
11149
|
+
|
|
11135
11150
|
function openContextMenu(e, lyr, parent) {
|
|
11136
|
-
var menu = new ContextMenu(parent);
|
|
11137
11151
|
if (e.contextMenuId && e.contextMenuId == openMenuId) {
|
|
11138
11152
|
closeOpenMenu(true);
|
|
11139
11153
|
return;
|
|
11140
11154
|
}
|
|
11141
11155
|
closeOpenMenu(true);
|
|
11142
|
-
|
|
11156
|
+
getContextMenu(parent).open(e, lyr);
|
|
11143
11157
|
openMenuId = e.contextMenuId || null;
|
|
11144
11158
|
}
|
|
11145
11159
|
|
|
@@ -11151,6 +11165,14 @@
|
|
|
11151
11165
|
var _open = false;
|
|
11152
11166
|
var _openCount = 0;
|
|
11153
11167
|
|
|
11168
|
+
// The menu is hidden until it is opened. A visible empty menu is 10px of
|
|
11169
|
+
// padding and border at the end of the page, and the page is exactly as tall
|
|
11170
|
+
// as the window, so those 10px make the document scrollable. The body is
|
|
11171
|
+
// overflow:hidden, but that only stops the user from scrolling: the browser
|
|
11172
|
+
// still scrolls the page to reveal a focused element or a caret, and once it
|
|
11173
|
+
// does, the whole UI stays shifted up (mbloch/mapshaper#702).
|
|
11174
|
+
menu.hide();
|
|
11175
|
+
|
|
11154
11176
|
this.isOpen = function() {
|
|
11155
11177
|
return _open;
|
|
11156
11178
|
};
|
|
@@ -11292,10 +11314,23 @@
|
|
|
11292
11314
|
}
|
|
11293
11315
|
menu.css('top', (e.pageY - offs.top - 15) + 'px');
|
|
11294
11316
|
menu.show();
|
|
11317
|
+
keepInsideWindow();
|
|
11295
11318
|
|
|
11296
11319
|
_open = true;
|
|
11297
11320
|
_openCount++;
|
|
11298
11321
|
|
|
11322
|
+
// A menu opened near the bottom of the window would otherwise hang off the
|
|
11323
|
+
// screen, out of reach and adding to the page's scrollable overflow.
|
|
11324
|
+
function keepInsideWindow() {
|
|
11325
|
+
var pad = 6;
|
|
11326
|
+
var rect = menu.node().getBoundingClientRect();
|
|
11327
|
+
var overflow = rect.bottom + pad - document.documentElement.clientHeight;
|
|
11328
|
+
var shift = Math.min(overflow, rect.top - pad);
|
|
11329
|
+
if (shift > 0) {
|
|
11330
|
+
menu.css('top', (parseFloat(menu.node().style.top) - shift) + 'px');
|
|
11331
|
+
}
|
|
11332
|
+
}
|
|
11333
|
+
|
|
11299
11334
|
function getParentOffset() { // crossbrowser version
|
|
11300
11335
|
if (parent == body) {
|
|
11301
11336
|
return {top: 0, left: 0};
|
|
@@ -25418,7 +25453,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
25418
25453
|
gui.messages = new MessageControl(gui);
|
|
25419
25454
|
gui.basemap = new Basemap(gui);
|
|
25420
25455
|
gui.session = new SessionHistory(gui);
|
|
25421
|
-
gui.contextMenu =
|
|
25456
|
+
gui.contextMenu = getContextMenu();
|
|
25422
25457
|
gui.undo = new Undo(gui);
|
|
25423
25458
|
gui.map = new MshpMap(gui);
|
|
25424
25459
|
// gui.project = new ProjectOptions(gui);
|
package/www/mapshaper.js
CHANGED
|
@@ -18168,10 +18168,29 @@
|
|
|
18168
18168
|
parseSizeParam: parseSizeParam
|
|
18169
18169
|
});
|
|
18170
18170
|
|
|
18171
|
-
//
|
|
18172
|
-
// length.
|
|
18171
|
+
// Conservative width as a fraction of the layer's median polygon-ring segment
|
|
18172
|
+
// length. Used for partition min-width and duplicate-boundary search — not for
|
|
18173
|
+
// the default gap-width=auto fill threshold (see getDefaultGapWidth).
|
|
18173
18174
|
var GAP_WIDTH_SEGMENT_FRACTION = 0.01;
|
|
18174
18175
|
|
|
18176
|
+
// Inject gap_width=auto unless the caller already chose a width or a legacy
|
|
18177
|
+
// area/sliver option. Used by -clean, -filter-slivers, and gap partition.
|
|
18178
|
+
function applyDefaultGapWidthOpts(opts) {
|
|
18179
|
+
opts = Object.assign({}, opts);
|
|
18180
|
+
if (opts.gap_width != null) return opts;
|
|
18181
|
+
// Legacy area/sliver options: keep the historical gap_fill_area=auto default.
|
|
18182
|
+
if (opts.gap_fill_area != null || opts.min_gap_area != null ||
|
|
18183
|
+
opts.min_area != null || opts.sliver_control != null) {
|
|
18184
|
+
if (opts.gap_fill_area == null && opts.min_gap_area == null &&
|
|
18185
|
+
opts.min_area == null) {
|
|
18186
|
+
opts.gap_fill_area = 'auto';
|
|
18187
|
+
}
|
|
18188
|
+
return opts;
|
|
18189
|
+
}
|
|
18190
|
+
opts.gap_width = 'auto';
|
|
18191
|
+
return opts;
|
|
18192
|
+
}
|
|
18193
|
+
|
|
18175
18194
|
// Used by -clean -dissolve -filter-slivers -filter-islands to generate filters
|
|
18176
18195
|
// for removing small polygon rings / filling mosaic gaps.
|
|
18177
18196
|
// Prefers gap_width= (including 'auto'). Legacy gap_fill_area / sliver_control
|
|
@@ -18317,9 +18336,10 @@
|
|
|
18317
18336
|
};
|
|
18318
18337
|
}
|
|
18319
18338
|
|
|
18320
|
-
//
|
|
18339
|
+
// Width equivalent of the legacy gap-fill-area=auto + sliver-control=1 test:
|
|
18340
|
+
// W = sqrt(A0 / π). The two filters accept the same rings (see getGapWidthTest).
|
|
18321
18341
|
function getDefaultGapWidth(lyr, arcs) {
|
|
18322
|
-
return
|
|
18342
|
+
return Math.sqrt(getDefaultSliverThreshold(lyr, arcs) / Math.PI);
|
|
18323
18343
|
}
|
|
18324
18344
|
|
|
18325
18345
|
function getMedianPolygonSegmentLength(lyr, arcs) {
|
|
@@ -18400,6 +18420,7 @@
|
|
|
18400
18420
|
var Slivers = /*#__PURE__*/Object.freeze({
|
|
18401
18421
|
__proto__: null,
|
|
18402
18422
|
GAP_WIDTH_SEGMENT_FRACTION: GAP_WIDTH_SEGMENT_FRACTION,
|
|
18423
|
+
applyDefaultGapWidthOpts: applyDefaultGapWidthOpts,
|
|
18403
18424
|
calcMaxSliverArea: calcMaxSliverArea,
|
|
18404
18425
|
getDefaultGapWidth: getDefaultGapWidth,
|
|
18405
18426
|
getDefaultSliverThreshold: getDefaultSliverThreshold,
|
|
@@ -23467,8 +23488,7 @@
|
|
|
23467
23488
|
var spherical = isLatLngCRS(crs);
|
|
23468
23489
|
var medianSeg = measureMedianSegment(lyr, arcs);
|
|
23469
23490
|
// Same width as interior gap filling, from the same automatic default.
|
|
23470
|
-
var distance = resolveCloseDistance(opts, crs,
|
|
23471
|
-
medianSeg * GAP_WIDTH_SEGMENT_FRACTION);
|
|
23491
|
+
var distance = resolveCloseDistance(opts, crs, getDefaultGapWidth(lyr, arcs));
|
|
23472
23492
|
if (!(distance > 0)) return 0;
|
|
23473
23493
|
|
|
23474
23494
|
profileStart('cg.outsideFacingArcs');
|
|
@@ -23518,7 +23538,7 @@
|
|
|
23518
23538
|
return {seams: seams, seeds: seeds, arcsById: arcsById, footpoints: footpoints};
|
|
23519
23539
|
}
|
|
23520
23540
|
|
|
23521
|
-
//
|
|
23541
|
+
// Median of every polygon-ring segment; used for min-seam length.
|
|
23522
23542
|
function measureMedianSegment(lyr, arcs) {
|
|
23523
23543
|
return profileWrap('cg.medianSegment', function() {
|
|
23524
23544
|
return getMedianPolygonSegmentLength(lyr, arcs);
|
|
@@ -24154,18 +24174,7 @@
|
|
|
24154
24174
|
function partitionPolygonMosaicGaps(lyr, dataset, nodes, opts) {
|
|
24155
24175
|
if (!lyr.shapes || lyr.shapes.length < MIN_GAP_OWNERS) return false;
|
|
24156
24176
|
var mosaicIndex = new MosaicIndex(lyr, nodes, {flat: true});
|
|
24157
|
-
var sliverOpts =
|
|
24158
|
-
if (sliverOpts.gap_width == null) {
|
|
24159
|
-
if (sliverOpts.gap_fill_area != null || sliverOpts.min_gap_area != null ||
|
|
24160
|
-
sliverOpts.min_area != null || sliverOpts.sliver_control != null) {
|
|
24161
|
-
if (sliverOpts.gap_fill_area == null && sliverOpts.min_gap_area == null &&
|
|
24162
|
-
sliverOpts.min_area == null) {
|
|
24163
|
-
sliverOpts.gap_fill_area = 'auto';
|
|
24164
|
-
}
|
|
24165
|
-
} else {
|
|
24166
|
-
sliverOpts.gap_width = 'auto';
|
|
24167
|
-
}
|
|
24168
|
-
}
|
|
24177
|
+
var sliverOpts = applyDefaultGapWidthOpts(opts);
|
|
24169
24178
|
var filter = getSliverFilter(lyr, dataset, sliverOpts).filter;
|
|
24170
24179
|
var gaps = mosaicIndex.getUnusedTileData(filter).filter(function(gap) {
|
|
24171
24180
|
return countOwners(gap.boundary) >= MIN_GAP_OWNERS;
|
|
@@ -24601,10 +24610,11 @@
|
|
|
24601
24610
|
// several features with hairline slivers, which is worse for having more of them.
|
|
24602
24611
|
//
|
|
24603
24612
|
// A layer's median segment length is the scale at which its boundaries were
|
|
24604
|
-
// drawn
|
|
24605
|
-
//
|
|
24606
|
-
//
|
|
24607
|
-
// effect: raising that to fill wider gaps should not coarsen
|
|
24613
|
+
// drawn. A small fraction of that scale marks where dividing a gap stops being
|
|
24614
|
+
// worth it: the displacement would be smaller than the data's own detail, and
|
|
24615
|
+
// the pieces would be hairline slivers. This is deliberately independent of
|
|
24616
|
+
// the gap-width in effect: raising that to fill wider gaps should not coarsen
|
|
24617
|
+
// the data's own scale.
|
|
24608
24618
|
function getMinDivisibleGapWidth(lyr, arcs) {
|
|
24609
24619
|
var width = getMedianPolygonSegmentLength(lyr, arcs) *
|
|
24610
24620
|
GAP_WIDTH_SEGMENT_FRACTION;
|
|
@@ -24741,7 +24751,7 @@
|
|
|
24741
24751
|
|
|
24742
24752
|
function cleanPolygonLayerGeometry(lyr, dataset, opts) {
|
|
24743
24753
|
// clean polygons by apply the 'dissolve2' function to each feature
|
|
24744
|
-
opts =
|
|
24754
|
+
opts = applyDefaultGapWidthOpts(opts);
|
|
24745
24755
|
var groups = lyr.shapes.map(function(shp, i) {
|
|
24746
24756
|
return [i];
|
|
24747
24757
|
});
|
|
@@ -24750,22 +24760,6 @@
|
|
|
24750
24760
|
markLayerChanged(lyr, {operation: 'cleanPolygonLayerGeometry', unit: 'shapes'});
|
|
24751
24761
|
}
|
|
24752
24762
|
|
|
24753
|
-
function withDefaultGapWidth(opts) {
|
|
24754
|
-
opts = Object.assign({}, opts);
|
|
24755
|
-
if (opts.gap_width != null) return opts;
|
|
24756
|
-
// Legacy area/sliver options: keep the historical gap_fill_area=auto default.
|
|
24757
|
-
if (opts.gap_fill_area != null || opts.min_gap_area != null ||
|
|
24758
|
-
opts.min_area != null || opts.sliver_control != null) {
|
|
24759
|
-
if (opts.gap_fill_area == null && opts.min_gap_area == null &&
|
|
24760
|
-
opts.min_area == null) {
|
|
24761
|
-
opts.gap_fill_area = 'auto';
|
|
24762
|
-
}
|
|
24763
|
-
return opts;
|
|
24764
|
-
}
|
|
24765
|
-
opts.gap_width = 'auto';
|
|
24766
|
-
return opts;
|
|
24767
|
-
}
|
|
24768
|
-
|
|
24769
24763
|
function collectArcIds(shapes) {
|
|
24770
24764
|
var ids = {};
|
|
24771
24765
|
traversePaths(shapes, function(o) {
|
|
@@ -40329,12 +40323,13 @@ ${svg}
|
|
|
40329
40323
|
|
|
40330
40324
|
parser.command('filter-slivers')
|
|
40331
40325
|
.describe('remove small polygon rings')
|
|
40326
|
+
.option('gap-width', gapWidthOpt)
|
|
40332
40327
|
.option('min-area', {
|
|
40333
40328
|
type: 'area',
|
|
40334
|
-
describe: '
|
|
40329
|
+
describe: '(deprecated) use gap-width= instead'
|
|
40335
40330
|
})
|
|
40336
40331
|
.option('sliver-control', {
|
|
40337
|
-
describe: '
|
|
40332
|
+
describe: '(deprecated) use gap-width= instead',
|
|
40338
40333
|
type: 'number'
|
|
40339
40334
|
})
|
|
40340
40335
|
.option('weighted', {
|
|
@@ -40383,9 +40378,9 @@ ${svg}
|
|
|
40383
40378
|
.option('no-replace', noReplaceOpt);
|
|
40384
40379
|
|
|
40385
40380
|
parser.command('grid')
|
|
40386
|
-
.describe('create a grid of square, hexagonal, rhombus or
|
|
40381
|
+
.describe('create a grid of square, hexagonal, rhombus, triangle or cairo polygons')
|
|
40387
40382
|
.option('type', {
|
|
40388
|
-
describe: 'square, square2, hex, hex2, rhombus, rhombus2, triangle or
|
|
40383
|
+
describe: 'square, square2, hex, hex2, rhombus, rhombus2, triangle, triangle2 or cairo (default is square)'
|
|
40389
40384
|
})
|
|
40390
40385
|
.option('interval', {
|
|
40391
40386
|
describe: 'side length (e.g. 500m, 12km)',
|
|
@@ -40407,6 +40402,10 @@ ${svg}
|
|
|
40407
40402
|
describe: 'scale factor for cells, between 0 and 2',
|
|
40408
40403
|
type: 'number'
|
|
40409
40404
|
})
|
|
40405
|
+
.option('rotate', {
|
|
40406
|
+
describe: 'rotation angle in degrees (default is 0)',
|
|
40407
|
+
type: 'number'
|
|
40408
|
+
})
|
|
40410
40409
|
// .option('bbox', {
|
|
40411
40410
|
// type: 'bbox',
|
|
40412
40411
|
// describe: 'xmin,ymin,xmax,ymax (default is bbox of data)'
|
|
@@ -56569,7 +56568,13 @@ ${svg}
|
|
|
56569
56568
|
};
|
|
56570
56569
|
|
|
56571
56570
|
function filterSlivers(lyr, dataset, optsArg) {
|
|
56572
|
-
var opts =
|
|
56571
|
+
var opts = applyDefaultGapWidthOpts(optsArg);
|
|
56572
|
+
// Legacy area path only: keep the historical sliver-control=1 default.
|
|
56573
|
+
// Do not inject it before applyDefaultGapWidthOpts, or the implicit default
|
|
56574
|
+
// would lock the command onto the area path.
|
|
56575
|
+
if (opts.gap_width == null && opts.sliver_control == null) {
|
|
56576
|
+
opts.sliver_control = 1;
|
|
56577
|
+
}
|
|
56573
56578
|
var filterData = getSliverFilter(lyr, dataset, opts);
|
|
56574
56579
|
var ringTest = filterData.filter;
|
|
56575
56580
|
var removed = 0;
|
|
@@ -74555,7 +74560,7 @@ ${svg}
|
|
|
74555
74560
|
cmd.polygonGrid = function(targetLayers, targetDataset, opts) {
|
|
74556
74561
|
requireProjectedDataset(targetDataset);
|
|
74557
74562
|
var params = getGridParams(targetLayers, targetDataset, opts);
|
|
74558
|
-
var gridDataset = makeGridDataset(params); // grid is a new dataset
|
|
74563
|
+
var gridDataset = makeGridDataset(params, opts); // grid is a new dataset
|
|
74559
74564
|
gridDataset.info = copyDatasetInfo(targetDataset.info);
|
|
74560
74565
|
setOutputLayerName(gridDataset.layers[0], null, 'grid', opts);
|
|
74561
74566
|
if (opts.debug) gridDataset.layers.push(cmd.pointGrid2(targetLayers, targetDataset, opts));
|
|
@@ -74619,35 +74624,67 @@ ${svg}
|
|
|
74619
74624
|
|
|
74620
74625
|
function makeGridDataset(params, opts) {
|
|
74621
74626
|
var geojson, dataset;
|
|
74627
|
+
var rotation = opts.rotate || 0;
|
|
74628
|
+
var genParams = rotation ? getRotatedGridParams(params, rotation) : params;
|
|
74622
74629
|
if (params.type == 'square') {
|
|
74623
|
-
geojson = getSquareGridGeoJSON(getSquareGridCoordinates(
|
|
74630
|
+
geojson = getSquareGridGeoJSON(getSquareGridCoordinates(genParams));
|
|
74624
74631
|
} else if (params.type == 'square2') {
|
|
74625
|
-
geojson = getRotatedSquareGridGeoJSON(
|
|
74632
|
+
geojson = getRotatedSquareGridGeoJSON(genParams);
|
|
74626
74633
|
} else if (params.type == 'hex') {
|
|
74627
|
-
geojson = getHexGridGeoJSON(getHexGridCoordinates(
|
|
74634
|
+
geojson = getHexGridGeoJSON(getHexGridCoordinates(genParams));
|
|
74628
74635
|
} else if (params.type == 'hex2') {
|
|
74629
74636
|
// use rotated grid
|
|
74630
|
-
geojson = getHexGridGeoJSON(getHexGridCoordinates(swapGridParams(
|
|
74637
|
+
geojson = getHexGridGeoJSON(getHexGridCoordinates(swapGridParams(genParams)));
|
|
74631
74638
|
swapPolygonCoords(geojson);
|
|
74632
74639
|
} else if (params.type == 'rhombus') {
|
|
74633
|
-
geojson = getRhombusGridGeoJSON(
|
|
74640
|
+
geojson = getRhombusGridGeoJSON(genParams, false);
|
|
74634
74641
|
} else if (params.type == 'rhombus2') {
|
|
74635
|
-
geojson = getRhombusGridGeoJSON(
|
|
74642
|
+
geojson = getRhombusGridGeoJSON(genParams, true);
|
|
74636
74643
|
} else if (params.type == 'triangle') {
|
|
74637
|
-
geojson = getTriangleGridGeoJSON(
|
|
74644
|
+
geojson = getTriangleGridGeoJSON(genParams, false);
|
|
74638
74645
|
} else if (params.type == 'triangle2') {
|
|
74639
|
-
geojson = getTriangleGridGeoJSON(
|
|
74646
|
+
geojson = getTriangleGridGeoJSON(genParams, true);
|
|
74647
|
+
} else if (params.type == 'cairo') {
|
|
74648
|
+
geojson = getCairoGridGeoJSON(genParams);
|
|
74640
74649
|
} else {
|
|
74641
74650
|
stop$1('Unsupported grid type');
|
|
74642
74651
|
}
|
|
74643
74652
|
scaleGridCells(geojson, params.cellScale);
|
|
74644
74653
|
alignGridToBounds(geojson, params.bbox);
|
|
74654
|
+
if (rotation) {
|
|
74655
|
+
rotateGridCells(geojson, rotation, params.bbox);
|
|
74656
|
+
}
|
|
74645
74657
|
cullGridCells(geojson, params.bbox);
|
|
74646
74658
|
dataset = importGeoJSON(geojson, {});
|
|
74647
74659
|
buildTopology(dataset);
|
|
74648
74660
|
return dataset;
|
|
74649
74661
|
}
|
|
74650
74662
|
|
|
74663
|
+
// Expand the generation rectangle so that, after rotation around the shared
|
|
74664
|
+
// center, the grid still covers the original target bbox.
|
|
74665
|
+
function getRotatedGridParams(params, rotation) {
|
|
74666
|
+
var angle = rotation * Math.PI / 180;
|
|
74667
|
+
var cos = Math.abs(Math.cos(angle));
|
|
74668
|
+
var sin = Math.abs(Math.sin(angle));
|
|
74669
|
+
return utils.defaults({
|
|
74670
|
+
width: params.width * cos + params.height * sin,
|
|
74671
|
+
height: params.width * sin + params.height * cos
|
|
74672
|
+
}, params);
|
|
74673
|
+
}
|
|
74674
|
+
|
|
74675
|
+
function rotateGridCells(geojson, rotation, bbox) {
|
|
74676
|
+
var cx = (bbox[0] + bbox[2]) / 2;
|
|
74677
|
+
var cy = (bbox[1] + bbox[3]) / 2;
|
|
74678
|
+
var transform = getAffineTransform(rotation, 1, [0, 0], [cx, cy]);
|
|
74679
|
+
geojson.geometries.forEach(function(geom) {
|
|
74680
|
+
if (geom.type == 'Polygon') {
|
|
74681
|
+
geom.coordinates[0] = geom.coordinates[0].map(function(xy) {
|
|
74682
|
+
return transform(xy[0], xy[1]);
|
|
74683
|
+
});
|
|
74684
|
+
}
|
|
74685
|
+
});
|
|
74686
|
+
}
|
|
74687
|
+
|
|
74651
74688
|
function swapGridParams(params) {
|
|
74652
74689
|
var bbox = params.bbox;
|
|
74653
74690
|
return utils.defaults({
|
|
@@ -74729,6 +74766,7 @@ ${svg}
|
|
|
74729
74766
|
if (type == 'hex' || type == 'hex2') return 3 * Math.sqrt(3) / 2;
|
|
74730
74767
|
if (type == 'rhombus' || type == 'rhombus2') return Math.sqrt(3) / 2;
|
|
74731
74768
|
if (type == 'triangle' || type == 'triangle2') return Math.sqrt(3) / 4;
|
|
74769
|
+
if (type == 'cairo') return (4 + Math.sqrt(7)) / 4;
|
|
74732
74770
|
stop$1('Unsupported grid type');
|
|
74733
74771
|
}
|
|
74734
74772
|
|
|
@@ -74882,6 +74920,95 @@ ${svg}
|
|
|
74882
74920
|
return geojson;
|
|
74883
74921
|
}
|
|
74884
74922
|
|
|
74923
|
+
// Equilateral type-4 Cairo tessellation. Four-valent (90°) vertices sit on a
|
|
74924
|
+
// square lattice of spacing S = interval * (√7+1)/2; each lattice square
|
|
74925
|
+
// contains a central bar of length `interval`, alternating horizontal and
|
|
74926
|
+
// vertical. Two pentagons share each bar.
|
|
74927
|
+
//
|
|
74928
|
+
// Vertices are computed once and reused. Algebraically equal expressions
|
|
74929
|
+
// such as i*S and (i-1)*S + S are not bit-identical, and independently
|
|
74930
|
+
// computed copies of the same 4-way junction become T-intersections.
|
|
74931
|
+
function getCairoGridGeoJSON(params) {
|
|
74932
|
+
var side = params.interval;
|
|
74933
|
+
var spacing = side * (Math.sqrt(7) + 1) / 2;
|
|
74934
|
+
var halfBar = side / 2;
|
|
74935
|
+
var iMin = -1;
|
|
74936
|
+
var jMin = -1;
|
|
74937
|
+
var iMax = Math.ceil(params.width / spacing) + 1;
|
|
74938
|
+
var jMax = Math.ceil(params.height / spacing) + 1;
|
|
74939
|
+
var lattice = [];
|
|
74940
|
+
var bars = [];
|
|
74941
|
+
var geometries = [];
|
|
74942
|
+
var i, j, y, cx, cy;
|
|
74943
|
+
|
|
74944
|
+
for (j = jMin - 1; j <= jMax + 1; j++) {
|
|
74945
|
+
y = j * spacing;
|
|
74946
|
+
lattice[j] = [];
|
|
74947
|
+
for (i = iMin - 1; i <= iMax + 1; i++) {
|
|
74948
|
+
lattice[j][i] = [i * spacing, y];
|
|
74949
|
+
}
|
|
74950
|
+
}
|
|
74951
|
+
|
|
74952
|
+
for (j = jMin - 1; j < jMax + 1; j++) {
|
|
74953
|
+
bars[j] = [];
|
|
74954
|
+
for (i = iMin - 1; i < iMax + 1; i++) {
|
|
74955
|
+
cx = (i + 0.5) * spacing;
|
|
74956
|
+
cy = (j + 0.5) * spacing;
|
|
74957
|
+
bars[j][i] = (i + j) % 2 === 0 ? {
|
|
74958
|
+
a: [cx - halfBar, cy],
|
|
74959
|
+
b: [cx + halfBar, cy]
|
|
74960
|
+
} : {
|
|
74961
|
+
a: [cx, cy - halfBar],
|
|
74962
|
+
b: [cx, cy + halfBar]
|
|
74963
|
+
};
|
|
74964
|
+
}
|
|
74965
|
+
}
|
|
74966
|
+
|
|
74967
|
+
for (j = jMin; j < jMax; j++) {
|
|
74968
|
+
for (i = iMin; i < iMax; i++) {
|
|
74969
|
+
if ((i + j) % 2 === 0) {
|
|
74970
|
+
appendCairoPentagon(geometries, [
|
|
74971
|
+
lattice[j][i],
|
|
74972
|
+
bars[j - 1][i].b,
|
|
74973
|
+
lattice[j][i + 1],
|
|
74974
|
+
bars[j][i].b,
|
|
74975
|
+
bars[j][i].a
|
|
74976
|
+
]);
|
|
74977
|
+
appendCairoPentagon(geometries, [
|
|
74978
|
+
lattice[j + 1][i],
|
|
74979
|
+
bars[j][i].a,
|
|
74980
|
+
bars[j][i].b,
|
|
74981
|
+
lattice[j + 1][i + 1],
|
|
74982
|
+
bars[j + 1][i].a
|
|
74983
|
+
]);
|
|
74984
|
+
} else {
|
|
74985
|
+
appendCairoPentagon(geometries, [
|
|
74986
|
+
lattice[j][i],
|
|
74987
|
+
bars[j][i].a,
|
|
74988
|
+
bars[j][i].b,
|
|
74989
|
+
lattice[j + 1][i],
|
|
74990
|
+
bars[j][i - 1].b
|
|
74991
|
+
]);
|
|
74992
|
+
appendCairoPentagon(geometries, [
|
|
74993
|
+
lattice[j][i + 1],
|
|
74994
|
+
bars[j][i + 1].a,
|
|
74995
|
+
lattice[j + 1][i + 1],
|
|
74996
|
+
bars[j][i].b,
|
|
74997
|
+
bars[j][i].a
|
|
74998
|
+
]);
|
|
74999
|
+
}
|
|
75000
|
+
}
|
|
75001
|
+
}
|
|
75002
|
+
return {type: 'GeometryCollection', geometries: geometries};
|
|
75003
|
+
}
|
|
75004
|
+
|
|
75005
|
+
function appendCairoPentagon(geometries, verts) {
|
|
75006
|
+
geometries.push({
|
|
75007
|
+
type: 'Polygon',
|
|
75008
|
+
coordinates: [verts.concat([verts[0]])]
|
|
75009
|
+
});
|
|
75010
|
+
}
|
|
75011
|
+
|
|
74885
75012
|
function subdivideHexagon(coords) {
|
|
74886
75013
|
var center = getPolygonRingCenter(coords);
|
|
74887
75014
|
return [
|
|
@@ -80084,7 +80211,7 @@ ${svg}
|
|
|
80084
80211
|
return name == 'rectangle' || name == 'rectangles' || name == 'filter' && opts.cleanup;
|
|
80085
80212
|
}
|
|
80086
80213
|
|
|
80087
|
-
var version = "0.7.
|
|
80214
|
+
var version = "0.7.60";
|
|
80088
80215
|
|
|
80089
80216
|
// Parse command line args into commands and run them
|
|
80090
80217
|
// Function takes an optional Node-style callback. A Promise is returned if no callback is given.
|