Skip to content

Commit

Permalink
v0.6.85
Browse files Browse the repository at this point in the history
  • Loading branch information
mbloch committed Apr 4, 2024
1 parent 8a092af commit 1b6f5bd
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 15 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
v0.6.85
* Added -rectangles bbox=<expression> option, which uses an expression to generate rectangle coords for each feature.

v0.6.84
* Catch a polygon drawing error.

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mapshaper",
"version": "0.6.84",
"version": "0.6.85",
"description": "A tool for editing vector datasets for mapping and GIS.",
"keywords": [
"shapefile",
Expand Down
5 changes: 4 additions & 1 deletion src/cli/mapshaper-options.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1330,8 +1330,11 @@ export function getOptionParser() {
.option('no-replace', noReplaceOpt);

parser.command('rectangles')
.describe('create a rectangle around each feature in a layer')
.describe('create a rectangle for each feature in a layer')
.option('offset', offsetOpt)
.option('bbox', {
describe: 'Use an expression to generate a rectangle for each feature'
})
.option('aspect-ratio', aspectRatioOpt)
.option('name', nameOpt)
.option('target', targetOpt)
Expand Down
59 changes: 48 additions & 11 deletions src/commands/mapshaper-rectangle.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import {
layerHasGeometry,
setOutputLayerName,
initDataTable,
layerIsRectangle
layerIsRectangle,
getFeatureCount
} from '../dataset/mapshaper-layer-utils';
import { mergeDatasetsIntoDataset } from '../dataset/mapshaper-merging';
import { importGeoJSON } from '../geojson/geojson-import';
Expand All @@ -17,21 +18,24 @@ import { probablyDecimalDegreeBounds, clampToWorldBounds } from '../geom/mapshap
import { Bounds } from '../geom/mapshaper-bounds';
import { densifyPathByInterval } from '../crs/mapshaper-densify';
import { bboxToCoords } from '../paths/mapshaper-rectangle-utils';
import { compileFeatureExpression } from '../expressions/mapshaper-feature-expressions';

// Create rectangles around each feature in a layer
cmd.rectangles = function(targetLyr, targetDataset, opts) {
if (!layerHasGeometry(targetLyr)) {
stop("Layer is missing geometric shapes");
}
var crsInfo = getDatasetCrsInfo(targetDataset);
var records = targetLyr.data ? targetLyr.data.getRecords() : null;
var geometries = targetLyr.shapes.map(function(shp) {
var bounds = targetLyr.geometry_type == 'point' ?
getPointFeatureBounds(shp) : targetDataset.arcs.getMultiShapeBounds(shp);
bounds = applyRectangleOptions(bounds, crsInfo.crs, opts);
if (!bounds) return null;
return bboxToPolygon(bounds.toArray(), opts);
});
var geometries;

if (opts.bbox) {
geometries = bboxExpressionToGeometries(opts.bbox, targetLyr, targetDataset, opts);

} else {
if (!layerHasGeometry(targetLyr)) {
stop("Layer is missing geometric shapes");
}
geometries = shapesToBoxGeometries(targetLyr, targetDataset, opts);
}

var geojson = {
type: 'FeatureCollection',
features: geometries.map(function(geom, i) {
Expand All @@ -53,6 +57,39 @@ cmd.rectangles = function(targetLyr, targetDataset, opts) {
return outputLayers;
};

function shapesToBoxGeometries(lyr, dataset, opts) {
var crsInfo = getDatasetCrsInfo(dataset);
return lyr.shapes.map(function(shp) {
var bounds = lyr.geometry_type == 'point' ?
getPointFeatureBounds(shp) : dataset.arcs.getMultiShapeBounds(shp);
bounds = applyRectangleOptions(bounds, crsInfo.crs, opts);
if (!bounds) return null;
return bboxToPolygon(bounds.toArray(), opts);
});
}

function bboxExpressionToGeometries(exp, lyr, dataset, opts) {
var compiled = compileFeatureExpression(exp, lyr, dataset.arcs, {});
var n = getFeatureCount(lyr);
var result;
var geometries = [];
for (var i=0; i<n; i++) {
result = compiled(i);
if (!looksLikeBbox(result)) {
stop('Invalid bbox value (expected a GeoJSON-type bbox):', result);
}
geometries.push(bboxToPolygon(result));
}
return geometries;
}

function looksLikeBbox(o) {
if (!o || o.length != 4) return false;
if (o.some(isNaN)) return false;
if (o[0] <= o[2] == false || o[1] <= o[3] == false) return false;
return true;
}

// Create rectangles around one or more target layers
//
cmd.rectangle2 = function(target, opts) {
Expand Down
9 changes: 9 additions & 0 deletions test/rectangle-test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ var Bounds = api.internal.Bounds;

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

describe('-rectangles command with bbox= option', async function() {
var csv = 'bbox\n"[0,0,1,1]"\n"[2,0,3,1]"';
var cmd = '-i data.csv -rectangles bbox="JSON.parse(bbox)" -o data.json';
var result = await api.applyCommands(cmd, {'data.csv': csv});
var json = JSON.parse(result['data.json']);
var coords = json.features[1].geometry.coordinates;
assert.deepEqual(coords, [[[2, 0], [3, 0], [3, 1], [2, 1], [2, 0]]]);
});

describe('applyAspectRatio()', function () {
var applyAspectRatio = api.internal.applyAspectRatio;
it('Handle max ratio', function () {
Expand Down

0 comments on commit 1b6f5bd

Please sign in to comment.