diff --git a/src/cli/mapshaper-options.js b/src/cli/mapshaper-options.js index d9d4d46e9..5c2d2da45 100644 --- a/src/cli/mapshaper-options.js +++ b/src/cli/mapshaper-options.js @@ -903,6 +903,9 @@ internal.getOptionParser = function() { .option("line-height", { describe: 'line spacing of multi-line labels (default is 1.1em)' }) + .option("rotate", { + describe: "(SVG) rotation in degrees applied on symbol (default is 0, clockwise)" + }) .option("target", targetOpt); parser.command("target") diff --git a/src/svg/geojson-to-svg.js b/src/svg/geojson-to-svg.js index c41d2c75d..ab958701b 100644 --- a/src/svg/geojson-to-svg.js +++ b/src/svg/geojson-to-svg.js @@ -163,19 +163,40 @@ SVG.importStandardPoint = function(coords, rec, layerOpts) { var symbolType = layerOpts.point_symbol || ''; var children = []; var halfSize = rec.r || 0; // radius or half of symbol size + var deg2rad = Math.PI / 180; + var symbolRotate = (+rec.rotate || 0) * deg2rad; // rotation applied on symbol around point's center var p; // if not a label, create a symbol even without a size // (circle radius can be set via CSS) if (halfSize > 0 || !isLabel) { if (symbolType == 'square') { + var squareRotate = symbolRotate + Math.PI * .25 // Rotate by 45 degrees to make the square sit straight + var squareLength = halfSize * Math.sqrt(2) // From the half-diagonal, get square's length p = { - tag: 'rect', + tag: 'polygon', properties: { - x: coords[0] - halfSize, - y: coords[1] - halfSize, - width: halfSize * 2, - height: halfSize * 2 - }}; + points: [ + [coords[0] + squareLength * Math.cos(squareRotate), coords[1] + squareLength * Math.sin(squareRotate)], + [coords[0] + squareLength * Math.cos(squareRotate + Math.PI * .5), coords[1] + squareLength * Math.sin(squareRotate + Math.PI * .5)], + [coords[0] + squareLength * Math.cos(squareRotate + Math.PI), coords[1] + squareLength * Math.sin(squareRotate + Math.PI)], + [coords[0] + squareLength * Math.cos(squareRotate + Math.PI * -.5), coords[1] + squareLength * Math.sin(squareRotate + Math.PI * -.5)] + ] + .map(function(pt) { + return pt.join(',') // joining two dimensions of point + }) + .join(',') // join each point string coordinates + } + }; + } else if (symbolType == 'line') { + var lineLength = Math.pow(halfSize, 2) // + p = { + tag: 'line', + properties: { + x1: coords[0], + y1: coords[1], + x2: coords[0] + lineLength * Math.cos(symbolRotate), + y2: coords[1] + lineLength * Math.sin(symbolRotate) + }}; } else { // default is circle p = { tag: 'circle', diff --git a/src/svg/svg-common.js b/src/svg/svg-common.js index 0beab7b5b..bead2cd8b 100644 --- a/src/svg/svg-common.js +++ b/src/svg/svg-common.js @@ -12,19 +12,20 @@ SVG.propertyTypes = { 'line-height': 'measure', 'letter-spacing': 'measure', 'stroke-width': 'number', - 'stroke-dasharray': 'dasharray' + 'stroke-dasharray': 'dasharray', + rotate: 'number' }; SVG.symbolRenderers = {}; SVG.furnitureRenderers = {}; -SVG.supportedProperties = 'class,opacity,stroke,stroke-width,stroke-dasharray,fill,r,dx,dy,font-family,font-size,text-anchor,font-weight,font-style,line-height,letter-spacing'.split(','); +SVG.supportedProperties = 'class,opacity,stroke,stroke-width,stroke-dasharray,fill,r,dx,dy,font-family,font-size,text-anchor,font-weight,font-style,line-height,letter-spacing,rotate'.split(','); SVG.commonProperties = 'class,opacity,stroke,stroke-width,stroke-dasharray'.split(','); SVG.propertiesBySymbolType = { polygon: utils.arrayToIndex(SVG.commonProperties.concat('fill')), polyline: utils.arrayToIndex(SVG.commonProperties), - point: utils.arrayToIndex(SVG.commonProperties.concat('fill', 'r')), + point: utils.arrayToIndex(SVG.commonProperties.concat('fill', 'r', 'rotate')), label: utils.arrayToIndex(SVG.commonProperties.concat( 'fill,r,font-family,font-size,text-anchor,font-weight,font-style,letter-spacing,dominant-baseline'.split(','))) }; diff --git a/test/geojson-to-svg-test.js b/test/geojson-to-svg-test.js index 53ef658bb..751d89d9b 100644 --- a/test/geojson-to-svg-test.js +++ b/test/geojson-to-svg-test.js @@ -78,8 +78,8 @@ describe('geojson-to-svg.js', function () { geometry: {type: 'Point', coordinates: [5, 5]} }; var expected = [{ - tag: 'rect', - properties: {x: 3, y: 3, width: 4, height: 4} + tag: 'polygon', + properties: {points: "7,7,3,7,2.9999999999999996,3,7,3"} }]; assert.deepEqual(SVG.importGeoJSONFeatures([geo], {point_symbol: 'square'}), expected) @@ -94,8 +94,8 @@ describe('geojson-to-svg.js', function () { var expected = [{ tag: 'g', children: [{ - tag: 'rect', - properties: {x: 3, y: 3, width: 4, height: 4} + tag: 'polygon', + properties: {points: "7,7,3,7,2.9999999999999996,3,7,3"} }, { tag: 'text', value: 'foo',