Skip to content

Commit 1b6f5bd

Browse files
committed
v0.6.85
1 parent 8a092af commit 1b6f5bd

6 files changed

+67
-15
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
v0.6.85
2+
* Added -rectangles bbox=<expression> option, which uses an expression to generate rectangle coords for each feature.
3+
14
v0.6.84
25
* Catch a polygon drawing error.
36

package-lock.json

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "mapshaper",
3-
"version": "0.6.84",
3+
"version": "0.6.85",
44
"description": "A tool for editing vector datasets for mapping and GIS.",
55
"keywords": [
66
"shapefile",

src/cli/mapshaper-options.mjs

+4-1
Original file line numberDiff line numberDiff line change
@@ -1330,8 +1330,11 @@ export function getOptionParser() {
13301330
.option('no-replace', noReplaceOpt);
13311331

13321332
parser.command('rectangles')
1333-
.describe('create a rectangle around each feature in a layer')
1333+
.describe('create a rectangle for each feature in a layer')
13341334
.option('offset', offsetOpt)
1335+
.option('bbox', {
1336+
describe: 'Use an expression to generate a rectangle for each feature'
1337+
})
13351338
.option('aspect-ratio', aspectRatioOpt)
13361339
.option('name', nameOpt)
13371340
.option('target', targetOpt)

src/commands/mapshaper-rectangle.mjs

+48-11
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import {
66
layerHasGeometry,
77
setOutputLayerName,
88
initDataTable,
9-
layerIsRectangle
9+
layerIsRectangle,
10+
getFeatureCount
1011
} from '../dataset/mapshaper-layer-utils';
1112
import { mergeDatasetsIntoDataset } from '../dataset/mapshaper-merging';
1213
import { importGeoJSON } from '../geojson/geojson-import';
@@ -17,21 +18,24 @@ import { probablyDecimalDegreeBounds, clampToWorldBounds } from '../geom/mapshap
1718
import { Bounds } from '../geom/mapshaper-bounds';
1819
import { densifyPathByInterval } from '../crs/mapshaper-densify';
1920
import { bboxToCoords } from '../paths/mapshaper-rectangle-utils';
21+
import { compileFeatureExpression } from '../expressions/mapshaper-feature-expressions';
2022

2123
// Create rectangles around each feature in a layer
2224
cmd.rectangles = function(targetLyr, targetDataset, opts) {
23-
if (!layerHasGeometry(targetLyr)) {
24-
stop("Layer is missing geometric shapes");
25-
}
2625
var crsInfo = getDatasetCrsInfo(targetDataset);
2726
var records = targetLyr.data ? targetLyr.data.getRecords() : null;
28-
var geometries = targetLyr.shapes.map(function(shp) {
29-
var bounds = targetLyr.geometry_type == 'point' ?
30-
getPointFeatureBounds(shp) : targetDataset.arcs.getMultiShapeBounds(shp);
31-
bounds = applyRectangleOptions(bounds, crsInfo.crs, opts);
32-
if (!bounds) return null;
33-
return bboxToPolygon(bounds.toArray(), opts);
34-
});
27+
var geometries;
28+
29+
if (opts.bbox) {
30+
geometries = bboxExpressionToGeometries(opts.bbox, targetLyr, targetDataset, opts);
31+
32+
} else {
33+
if (!layerHasGeometry(targetLyr)) {
34+
stop("Layer is missing geometric shapes");
35+
}
36+
geometries = shapesToBoxGeometries(targetLyr, targetDataset, opts);
37+
}
38+
3539
var geojson = {
3640
type: 'FeatureCollection',
3741
features: geometries.map(function(geom, i) {
@@ -53,6 +57,39 @@ cmd.rectangles = function(targetLyr, targetDataset, opts) {
5357
return outputLayers;
5458
};
5559

60+
function shapesToBoxGeometries(lyr, dataset, opts) {
61+
var crsInfo = getDatasetCrsInfo(dataset);
62+
return lyr.shapes.map(function(shp) {
63+
var bounds = lyr.geometry_type == 'point' ?
64+
getPointFeatureBounds(shp) : dataset.arcs.getMultiShapeBounds(shp);
65+
bounds = applyRectangleOptions(bounds, crsInfo.crs, opts);
66+
if (!bounds) return null;
67+
return bboxToPolygon(bounds.toArray(), opts);
68+
});
69+
}
70+
71+
function bboxExpressionToGeometries(exp, lyr, dataset, opts) {
72+
var compiled = compileFeatureExpression(exp, lyr, dataset.arcs, {});
73+
var n = getFeatureCount(lyr);
74+
var result;
75+
var geometries = [];
76+
for (var i=0; i<n; i++) {
77+
result = compiled(i);
78+
if (!looksLikeBbox(result)) {
79+
stop('Invalid bbox value (expected a GeoJSON-type bbox):', result);
80+
}
81+
geometries.push(bboxToPolygon(result));
82+
}
83+
return geometries;
84+
}
85+
86+
function looksLikeBbox(o) {
87+
if (!o || o.length != 4) return false;
88+
if (o.some(isNaN)) return false;
89+
if (o[0] <= o[2] == false || o[1] <= o[3] == false) return false;
90+
return true;
91+
}
92+
5693
// Create rectangles around one or more target layers
5794
//
5895
cmd.rectangle2 = function(target, opts) {

test/rectangle-test.mjs

+9
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@ var Bounds = api.internal.Bounds;
55

66
describe('mapshaper-rectangle.js', function () {
77

8+
describe('-rectangles command with bbox= option', async function() {
9+
var csv = 'bbox\n"[0,0,1,1]"\n"[2,0,3,1]"';
10+
var cmd = '-i data.csv -rectangles bbox="JSON.parse(bbox)" -o data.json';
11+
var result = await api.applyCommands(cmd, {'data.csv': csv});
12+
var json = JSON.parse(result['data.json']);
13+
var coords = json.features[1].geometry.coordinates;
14+
assert.deepEqual(coords, [[[2, 0], [3, 0], [3, 1], [2, 1], [2, 0]]]);
15+
});
16+
817
describe('applyAspectRatio()', function () {
918
var applyAspectRatio = api.internal.applyAspectRatio;
1019
it('Handle max ratio', function () {

0 commit comments

Comments
 (0)