diff --git a/CHANGELOG.md b/CHANGELOG.md index 05e17d0c..59048823 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +v0.6.67 +* -rectangle command with width= option creates a "frame" layer to use as the extent of an SVG map by setting the size of the SVG viewport (an alternative to using -o fit-extent= option). +* [web] Map symbols scale as the map zooms when a frame layer is visible. +* [web] Draggable box tool resizes symmetrically with the shift key pressed. +* [web] Allow selected layer to be temporarily hidden (via layers menu). + v0.6.66 * [web] Added ability to edit attribute data of multiple selected features ("select features" tool). * [web] Added interactive tool for adding points to point-type layer by clicking on the map. diff --git a/REFERENCE.md b/REFERENCE.md index db4fa166..3bd71cbf 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -1,6 +1,6 @@ # COMMAND REFERENCE -This documentation applies to version 0.6.66 of mapshaper's command line program. Run `mapshaper -v` to check your version. For an introduction to the command line tool, read [this page](https://github.com/mbloch/mapshaper/wiki/Introduction-to-the-Command-Line-Tool) first. +This documentation applies to version 0.6.67 of mapshaper's command line program. Run `mapshaper -v` to check your version. For an introduction to the command line tool, read [this page](https://github.com/mbloch/mapshaper/wiki/Introduction-to-the-Command-Line-Tool) first. ## Command line syntax @@ -1341,15 +1341,17 @@ Example: `hatches 45deg 2px red 2px grey` `font-size=` Size of label text (default is 12) -`font-family=` CSS font family of labels (default is sans-serif) +`font-family=` CSS font-family of labels (default is sans-serif) -`font-weight=` CSS font weight property of labels (e.g. bold, 700) +`font-weight=` CSS font-weight property of labels (e.g. bold, 700) -`font-style=` CSS font style property of labels (e.g. italic) +`font-style=` CSS font-style property of labels (e.g. italic) + +`font-stretch=` CSS font-stretch property of labels (e.g. condensed) `letter-spacing=` CSS letter-spacing property of labels -`line-height=` Line spacing of multi-line labels (default is 1.1em). Lines are separated by newline characters in the label text. +`line-height=` Line spacing of multi-line labels (default is 1.1em). Lines are separated by newline characters or `
` tags in the label text. Common options: `target=` diff --git a/package-lock.json b/package-lock.json index a726c1f4..60d8c089 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "mapshaper", - "version": "0.6.66", + "version": "0.6.67", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "mapshaper", - "version": "0.6.66", + "version": "0.6.67", "license": "MPL-2.0", "dependencies": { "@placemarkio/tokml": "^0.3.3", diff --git a/package.json b/package.json index f34b636c..e013f472 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mapshaper", - "version": "0.6.66", + "version": "0.6.67", "description": "A tool for editing vector datasets for mapping and GIS.", "keywords": [ "shapefile", diff --git a/src/cli/mapshaper-options.mjs b/src/cli/mapshaper-options.mjs index 472db256..f12d98b8 100644 --- a/src/cli/mapshaper-options.mjs +++ b/src/cli/mapshaper-options.mjs @@ -1318,8 +1318,8 @@ export function getOptionParser() { }) .option('offset', offsetOpt) .option('width', { - describe: 'set width of map in pixels, use rectangle as frame', - type: 'number' + describe: 'set width of map in pixels, use rectangle as frame' + // type: 'number' // use string, to allow units (e.g. in, px, pt) }) .option('aspect-ratio', aspectRatioOpt) .option('source', { @@ -1596,7 +1596,10 @@ export function getOptionParser() { .option('font-style', { describe: 'CSS font style property of labels (e.g. italic)' }) - .option('letter-spacing', { + .option('font-stretch', { + describe: 'CSS font stretch property of labels (e.g. condensed)' + }) + .option('letter-spacing', { describe: 'CSS letter-spacing property of labels' }) .option('line-height', { @@ -1888,20 +1891,20 @@ export function getOptionParser() { describe: 'frame coordinates (xmin,ymin,xmax,ymax)', type: 'bbox' }) - .option('offset', offsetOpt) + // .option('offset', offsetOpt) .option('width', { - describe: 'pixel width of output (default is 800)' - }) - .option('height', { - describe: 'pixel height of output (may be a range)' - }) - .option('pixels', { - describe: 'area of output in pixels (alternative to width and height)', - type: 'number' - }) - .option('source', { - describe: 'name of layer to enclose' + describe: 'width of output (default is 800px)' }) + // .option('height', { + // describe: 'pixel height of output (may be a range)' + // }) + // .option('pixels', { + // describe: 'area of output in pixels (alternative to width and height)', + // type: 'number' + // }) + // .option('source', { + // describe: 'name of layer to enclose' + // }) .option('name', nameOpt); parser.command('fuzzy-join') diff --git a/src/commands/mapshaper-rectangle.mjs b/src/commands/mapshaper-rectangle.mjs index 10f1264f..cd949bf0 100644 --- a/src/commands/mapshaper-rectangle.mjs +++ b/src/commands/mapshaper-rectangle.mjs @@ -1,5 +1,5 @@ import cmd from '../mapshaper-cmd'; -import { convertFourSides } from '../geom/mapshaper-units'; +import { convertFourSides, parseSizeParam } from '../geom/mapshaper-units'; import { setDatasetCrsInfo, getDatasetCrsInfo, getCrsInfo } from '../crs/mapshaper-projections'; import { getLayerBounds, @@ -58,7 +58,7 @@ cmd.rectangles = function(targetLyr, targetDataset, opts) { cmd.rectangle2 = function(target, opts) { // if target layer is a rectangle and we're applying frame properties, // turn the target into a frame instead of creating a new rectangle - if (target.layers.length == 1 && opts.width > 0 && + if (target.layers.length == 1 && opts.width && layerIsRectangle(target.layers[0], target.dataset.arcs)) { applyFrameProperties(target.layers[0], opts); return; @@ -100,10 +100,10 @@ cmd.rectangle = function(source, opts) { }; function applyFrameProperties(lyr, opts) { - if (opts.width > 0 === false) return; + if (!opts.width) return; if (!lyr.data) initDataTable(lyr); var d = lyr.data.getRecords()[0] || {}; - d.width = opts.width; + d.width = parseSizeParam(opts.width); d.type = 'frame'; } diff --git a/src/geom/mapshaper-units.mjs b/src/geom/mapshaper-units.mjs index 47401c57..46717ff0 100644 --- a/src/geom/mapshaper-units.mjs +++ b/src/geom/mapshaper-units.mjs @@ -23,10 +23,11 @@ var TO_METERS = { // str: display size in px, pt or in // using: 72pt per inch, 1pt per pixel. -export function parseSizeParam(str) { - var num = parseFloat(str), - units = /px$/.test(str) && 'px' || /pt$/.test(str) && 'pt' || - /in$/.test(str) && 'in' || !isNaN(+str) && 'px' || null; +export function parseSizeParam(p) { + var str = String(p), + num = parseFloat(str), + units = /px|pix/.test(str) && 'px' || /pt|point/.test(str) && 'pt' || + /in/.test(str) && 'in' || !isNaN(+str) && 'px' || null; if (isNaN(num) || !units) { stop('Invalid size:', str); } diff --git a/src/gui/gui-canvas.mjs b/src/gui/gui-canvas.mjs index beb799f3..3b7f1807 100644 --- a/src/gui/gui-canvas.mjs +++ b/src/gui/gui-canvas.mjs @@ -373,12 +373,12 @@ export function DisplayCanvas() { // (style.opacity < 1 ? '~' + style.opacity : '') + (style.fillPattern ? '~' + style.fillPattern : ''); } - return _self; } function getScaledLineScale(ext) { - return ext.getSymbolScale() || getLineScale(ext); + var previewScale = ext.getSymbolScale(); + return previewScale == 1 ? getLineScale(ext) : previewScale; } // Vary line width according to zoom ratio. @@ -556,7 +556,8 @@ function getPathStart(ext, lineScale) { if (pixRatio > 1) { // bump up thin lines on retina, but not to more than 1px // (tests on Chrome showed much faster rendering of 1px lines) - strokeWidth = strokeWidth < 1 ? 1 : strokeWidth * pixRatio; + // strokeWidth = strokeWidth < 1 ? 1 : strokeWidth * pixRatio; + strokeWidth = strokeWidth * pixRatio; } ctx.lineCap = style.lineCap || 'round'; ctx.lineJoin = style.lineJoin || 'round'; diff --git a/src/gui/gui-draw-lines.mjs b/src/gui/gui-draw-lines.mjs new file mode 100644 index 00000000..dda301d6 --- /dev/null +++ b/src/gui/gui-draw-lines.mjs @@ -0,0 +1,83 @@ +import { error, internal } from './gui-core'; +import { updatePointCoords } from './gui-display-utils'; + +export function initLineDrawing(gui, ext, mouse, hit) { + var _on = false; + var _coords; + var _lastClick; + + gui.on('interaction_mode_change', function(e) { + _on = e.mode === 'draw-lines'; + gui.container.findChild('.map-layers').classed('draw-lines', _on); + }); + + function active() { + return _on; + } + + function extending() { + return active() && !!_coords; + } + + function startPath(e) { + + } + + function extendPath(e) { + + } + + function finishPath() { + _coords = null; + } + + gui.keyboard.on('keydown', function(evt) { + if (!active()) return; + if (evt.keyCode == 27) { // esc + finishPath(); + } + }); + + mouse.on('click', function(e) { + if (!active()) return; + // console.log('[click]', e) + // addPoint(e.x, e.y); + // gui.dispatchEvent('map-needs-refresh'); + _lastClick = e; + }); + + // note: second click event is fired before this + mouse.on('dblclick', function(e) { + if (!active()) return; + // block navigation + e.stopPropagation(); + finishPath(); + + }, null, 3); // hit detection is priority 2 + + mouse.on('hover', function(e) { + if (!active()) return; + }); + + // x, y: pixel coordinates + function addPoint(x, y) { + var p = ext.translatePixelCoords(x, y); + var target = hit.getHitTarget(); + var lyr = target.layer; + var fid = lyr.shapes.length; + if (lyr.data) { + // this seems to work even for projected layers -- the data tables + // of projected and original data seem to be shared. + lyr.data.getRecords()[fid] = getEmptyDataRecord(lyr.data); + } + lyr.shapes[fid] = [p]; + updatePointCoords(target, fid); + } + + function getEmptyDataRecord(table) { + return table.getFields().reduce(function(memo, name) { + memo[name] = null; + return memo; + }, {}); + } +} diff --git a/src/gui/gui-drawing.mjs b/src/gui/gui-drawing.mjs index fbf69e72..14d7cad6 100644 --- a/src/gui/gui-drawing.mjs +++ b/src/gui/gui-drawing.mjs @@ -1,7 +1,7 @@ import { initPointDrawing } from './gui-draw-points'; -// import { initLineDrawing } from './gui-draw-lines'; +import { initLineDrawing } from './gui-draw-lines'; export function initDrawing(gui, ext, mouse, hit) { initPointDrawing(gui, ext, mouse, hit); - // initLineDrawing(gui, ext, mouse, hit); + initLineDrawing(gui, ext, mouse, hit); } diff --git a/src/svg/svg-properties.mjs b/src/svg/svg-properties.mjs index b9d005b0..7f28930b 100644 --- a/src/svg/svg-properties.mjs +++ b/src/svg/svg-properties.mjs @@ -18,6 +18,7 @@ var stylePropertyTypes = { 'font-family': null, 'font-size': null, 'font-style': null, + 'font-stretch': null, 'font-weight': null, 'label-text': null, // leaving this null 'letter-spacing': 'measure', @@ -67,7 +68,7 @@ var propertiesBySymbolType = { polyline: utils.arrayToIndex(commonProperties.concat('stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit')), point: utils.arrayToIndex(commonProperties.concat('fill', 'r')), label: utils.arrayToIndex(commonProperties.concat( - 'fill,font-family,font-size,text-anchor,font-weight,font-style,letter-spacing,dominant-baseline'.split(','))) + 'fill,font-family,font-size,text-anchor,font-weight,font-style,font-stretch,letter-spacing,dominant-baseline'.split(','))) }; // symType: point, polygon, polyline, label diff --git a/test/geojson-to-svg-test.mjs b/test/geojson-to-svg-test.mjs index 976a13cd..18849d42 100644 --- a/test/geojson-to-svg-test.mjs +++ b/test/geojson-to-svg-test.mjs @@ -206,6 +206,7 @@ describe('geojson-to-svg.js', function () { 'foo': 'bar', // should not be in output 'label-text': 'TBD', 'font-style': 'italic', + 'font-stretch': 'condensed', 'font-weight': 'bold', 'font-family': 'Gill Sans, sans-serif', // TODO: handle quotes dx: '10px', @@ -223,8 +224,12 @@ describe('geojson-to-svg.js', function () { value: 'TBD', // properties: {x: 0, y: 1, dx: '10px', dy: '-1em'} properties: { - transform: 'translate(0 1)', x: '10px', y: '-1em', - 'font-family': 'Gill Sans, sans-serif', 'font-style': 'italic', 'font-weight': 'bold' + transform: 'translate(0 1)', + x: '10px', y: '-1em', + 'font-family': 'Gill Sans, sans-serif', + 'font-style': 'italic', + 'font-weight': 'bold', + 'font-stretch': 'condensed' } }] }]; diff --git a/test/svg-test.mjs b/test/svg-test.mjs index 827bf1cd..25fea6d2 100644 --- a/test/svg-test.mjs +++ b/test/svg-test.mjs @@ -121,6 +121,41 @@ describe('mapshaper-svg.js', function () { }); }) + it ('-frame width= command sets viewport, accepts 4in as width', async function() { + var box = { + type: 'Polygon', + coordinates: [[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]] + }; + var line = { + type: 'Feature', + properties: null, + geometry: { + type: 'LineString', + coordinates: [[-10, -5], [5, 10]] + } + }; + var cmd = '-i line.json -i box.json -frame width=4in -target box,line -o map.svg'; + var out = await api.applyCommands(cmd, {'line.json': line, 'box.json': box}); + var svg = out['map.svg']; + assert(svg.includes('\n\n')) + assert(svg.includes('width="288" height="288" viewBox="0 0 288 288"')); + }) + + it ('-rectangle with width= property creates viewport, accepts 2inches as width', async function() { + var line = { + type: 'Feature', + properties: null, + geometry: { + type: 'LineString', + coordinates: [[-10, -5], [5, 10]] + } + }; + var cmd = '-i line.json -rectangle bbox=0,0,1,2 width=2inches -target rectangle,line -o map.svg'; + var out = await api.applyCommands(cmd, {'line.json': line}); + var svg = out['map.svg']; + assert(svg.includes('width="144" height="288" viewBox="0 0 144 288"')); + }) + it ('-o svg-bbox option', async function() { var geo = { type: 'Feature',