From 8bdcadde2f6e1f6fd1a10042e031c32ca0840b12 Mon Sep 17 00:00:00 2001 From: Zhenyang Hua Date: Tue, 12 Jan 2021 20:24:43 -0500 Subject: [PATCH 1/3] Added simple geo path. --- example/index.module.html | 126 ++++++++++++++------------------------ package-lock.json | 13 ++++ package.json | 1 + src/geometry.js | 12 ++++ src/helper.js | 50 ++++++++++++++- src/index.js | 44 +++++++++++++ src/index.scss | 7 +++ 7 files changed, 172 insertions(+), 81 deletions(-) diff --git a/example/index.module.html b/example/index.module.html index f8e7285..8fb2a21 100644 --- a/example/index.module.html +++ b/example/index.module.html @@ -2,101 +2,66 @@ Simple Map - - + + - + -
-
- - - - +
+
+ + + +
- diff --git a/package-lock.json b/package-lock.json index da29e59..ed47ec9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1884,6 +1884,11 @@ "array-find-index": "^1.0.1" } }, + "d3-array": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.9.1.tgz", + "integrity": "sha512-Ob7RdOtkqsjx1NWyQHMFLtCSk6/aKTxDdC4ZIolX+O+mDD2RzrsYgAyc0WGAlfYFVELLSilS7w8BtE3PKM8bHg==" + }, "d3-dispatch": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-2.0.0.tgz", @@ -1898,6 +1903,14 @@ "d3-selection": "2" } }, + "d3-geo": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-2.0.1.tgz", + "integrity": "sha512-M6yzGbFRfxzNrVhxDJXzJqSLQ90q1cCyb3EWFZ1LF4eWOBYxFypw7I/NFVBNXKNqxv1bqLathhYvdJ6DC+th3A==", + "requires": { + "d3-array": ">=2.5" + } + }, "d3-selection": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-2.0.0.tgz", diff --git a/package.json b/package.json index eb87f9f..79dcbba 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "homepage": "https://github.com/zhenyanghua/MeasureTool-GoogleMaps-V3#readme", "dependencies": { "d3-drag": "^2.0.0", + "d3-geo": "^2.0.1", "d3-selection": "^2.0.0" }, "devDependencies": { diff --git a/src/geometry.js b/src/geometry.js index e6d6244..858d476 100644 --- a/src/geometry.js +++ b/src/geometry.js @@ -12,6 +12,16 @@ export class Geometry { return segments; } + static toLineString(points) { + return { + "type": "Feature", + "geometry": { + "type": "LineString", + "coordinates": points + } + } + } + constructor() { this._nodes = []; } @@ -31,4 +41,6 @@ export class Geometry { insertNode(i, point) { this._nodes.splice(i, 0, point); } + + } diff --git a/src/helper.js b/src/helper.js index 6842e6d..0231174 100644 --- a/src/helper.js +++ b/src/helper.js @@ -196,7 +196,7 @@ export default class Helper { * @private */ static _interpolate(p1, p2, fraction) { - let point = google.maps.geometry.spherical.interpolate( + const point = google.maps.geometry.spherical.interpolate( new google.maps.LatLng(p1[1], p1[0]), new google.maps.LatLng(p2[1], p2[0]), fraction @@ -209,4 +209,52 @@ export default class Helper { // let y = m * x + b; // return [x, y]; } + + /** + * if last segment length (start with 0) adds current segment length is less than tick length, + * segment length = last segment length + segment length + * last segment length = segment length; + * move on to the next segment + * + * current point = compute the latlng using fraction (tick length - last segment length ) / current segment length + * between current segment start point and end point. + * segment length = segment length - (tick length - last segment length) + * + * While the given minimum tick length is less than the segment length, + * compute the latlng at the fraction between current point and the end point. + * push the latlng + * assign the latlng to current point. + * segment length subtracts tick length. + * + * @param segments + * @param length + * @param includeSegmentNodes + */ + interpolatePointsOnPath(segments, length, includeSegmentNodes = false) { + if (segments.length === 0) return []; + let lastSegmentLength = 0, curPoint = segments[0][0], points = []; + for (let i = 0; i < segments.length; i++) { + if (includeSegmentNodes) points.push(segments[i][0]); + let segmentLength = this.computeLengthBetween(segments[i][0], segments[i][1]); + if (lastSegmentLength + segmentLength < length) { + segmentLength += lastSegmentLength; + lastSegmentLength = segmentLength; + continue; + } + curPoint = Helper._interpolate( + segments[i][0], + segments[i][1], + (length - lastSegmentLength) / segmentLength); + segmentLength -= (length - lastSegmentLength); + points.push(curPoint); + while(length < segmentLength) { + curPoint = Helper._interpolate(curPoint, segments[i][1], length / segmentLength); + points.push(curPoint); + segmentLength -= length; + lastSegmentLength = segmentLength; + } + } + if (includeSegmentNodes) points.push(segments[segments.length - 1][1]); + return points; + } } diff --git a/src/index.js b/src/index.js index 1ff50a2..21e70e2 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,6 @@ import { drag } from 'd3-drag'; import { select, selectAll } from 'd3-selection'; +import { geoPath, geoTransform } from 'd3-geo'; import { Config } from './config'; import ContextMenu from './context-menu'; import Tooltip from './tooltip'; @@ -73,6 +74,7 @@ export default class MeasureTool { this._id = Helper.makeId(4); this._events = new Map(); this._geometry = new Geometry(); + this._computeTickLength(); this._init(); } @@ -275,6 +277,12 @@ export default class MeasureTool { .append('svg') .attr('class', `${Config.prefix}-svg-overlay`); + const { paths, gmPath } = this._getProjectedPath(); + this._tickPath = this._svgOverlay + .append('g') + .attr('class', 'tick-path'); + this._tickPath.selectAll("path").data(paths); + this._linesBase = this._svgOverlay.append('g').attr('class', 'base'); this._linesBase.selectAll('line').data(this._geometry.lines); @@ -328,6 +336,7 @@ export default class MeasureTool { this._updateCircles(); this._updateTouchCircles(); this._updateLine(); + this._updatePath(); if (this._options.showSegmentLength) { this._updateSegmentText(); } @@ -547,6 +556,35 @@ export default class MeasureTool { lineDrag.exit().remove(); } + _updatePath() { + const { paths, gmPath } = this._getProjectedPath(); + const tickPath = this._tickPath.selectAll('path') + .data(paths) + .attr("class", "tick-path") + .attr('d', gmPath); + tickPath.exit().remove(); + tickPath.enter() + .append('path') + .attr('d', gmPath); + } + + _getProjectedPath() { + const self = this; + this._computeTickLength(); + const points = this._helper.interpolatePointsOnPath( + this._geometry.lines, this._tickLength, true + ); + const paths = [Geometry.toLineString(points)]; + const gmTransform = geoTransform({ + point: function(lat, lng) { + const [x, y] = self._projectionUtility.latLngToSvgPoint([lat, lng]); + this.stream.point(x, y); + } + }); + const gmPath = geoPath().projection(gmTransform); + return { paths, gmPath }; + } + _updateSegmentText() { let text = this._segmentText .selectAll('text') @@ -1081,4 +1119,10 @@ export default class MeasureTool { }, }; } + + _computeTickLength() { + const metersPerPixel = 156543.03392 * Math.cos(this._map.getCenter().lat() * Math.PI / 180) + / Math.pow(2, this._map.getZoom()); + this._tickLength = metersPerPixel * 30; + } } diff --git a/src/index.scss b/src/index.scss index 75831d0..2a8f594 100644 --- a/src/index.scss +++ b/src/index.scss @@ -26,6 +26,13 @@ $alpha-black: rgba(0, 0, 0, 1); pointer-events: none; } +.tick-path { + fill: none; + stroke: red; + stroke-width: 6px; + pointer-events: none; +} + .base-line { fill: none; stroke: black; From 1e59e1317679e307a9ea4af36cdda07f7bffd1f4 Mon Sep 17 00:00:00 2001 From: Zhenyang Hua Date: Sat, 16 Jan 2021 10:13:11 -0500 Subject: [PATCH 2/3] WIP --- src/geometry.js | 14 +++ src/helper.js | 12 +++ src/index.js | 212 ++++++++++++++++++++++---------------- src/index.scss | 11 +- src/projection-utility.js | 4 +- 5 files changed, 160 insertions(+), 93 deletions(-) diff --git a/src/geometry.js b/src/geometry.js index 858d476..f146d18 100644 --- a/src/geometry.js +++ b/src/geometry.js @@ -42,5 +42,19 @@ export class Geometry { this._nodes.splice(i, 0, point); } + static equals(segments1, segments2) { + if (segments1.length !== segments2.length) { + return false; + } + for (let i = 0; i < segments1.length; i++) { + if (segments1[i][0][0] !== segments2[i][0][0] || + segments1[i][0][1] !== segments2[i][0][1] || + segments1[i][1][0] !== segments2[i][1][0] || + segments1[i][1][1] !== segments2[i][1][1]) { + return false; + } + } + return true; + } } diff --git a/src/helper.js b/src/helper.js index 0231174..04637e7 100644 --- a/src/helper.js +++ b/src/helper.js @@ -1,10 +1,12 @@ import { UnitTypeId } from './UnitTypeId'; +import { Geometry } from './geometry'; export default class Helper { constructor(options) { this._options = { unit: UnitTypeId.METRIC, }; Object.assign(this._options, options); + this._lastInterpolatedPoints = { segments: [], length: 0, points: [] }; this.init(); } @@ -231,6 +233,11 @@ export default class Helper { * @param includeSegmentNodes */ interpolatePointsOnPath(segments, length, includeSegmentNodes = false) { + if (this._lastInterpolatedPoints.length === length && + Geometry.equals(this._lastInterpolatedPoints.segments, segments)) { + return this._lastInterpolatedPoints.points; + } + console.debug('interpolatePointsOnPath called') if (segments.length === 0) return []; let lastSegmentLength = 0, curPoint = segments[0][0], points = []; for (let i = 0; i < segments.length; i++) { @@ -255,6 +262,11 @@ export default class Helper { } } if (includeSegmentNodes) points.push(segments[segments.length - 1][1]); + this._lastInterpolatedPoints = { + length, + segments, + points + }; return points; } } diff --git a/src/index.js b/src/index.js index 21e70e2..e7d5dbc 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,6 @@ import { drag } from 'd3-drag'; import { select, selectAll } from 'd3-selection'; -import { geoPath, geoTransform } from 'd3-geo'; +import { geoPath, geoTransform, geoClipRectangle } from 'd3-geo'; import { Config } from './config'; import ContextMenu from './context-menu'; import Tooltip from './tooltip'; @@ -67,7 +67,7 @@ export default class MeasureTool { initialSegments: [], language: navigator ? navigator.language : 'en', invertColor: false, - ...options, + ...options }; this._map = map; this._map.setClickableIcons(false); @@ -107,7 +107,7 @@ export default class MeasureTool { } this._helper = new Helper({ - unit: this._options.unit, + unit: this._options.unit }); this._initOverlay(); } @@ -281,7 +281,22 @@ export default class MeasureTool { this._tickPath = this._svgOverlay .append('g') .attr('class', 'tick-path'); - this._tickPath.selectAll("path").data(paths); + this._tickPath.selectAll('path').data(paths); + + this._ticks = this._svgOverlay + .append('g') + .attr('class', 'ticks'); + this._ticks.append('marker') + .attr('id', 'marker-small-ticks') + .attr('markerHeight', 3) + .attr('markerWidth', 1) + .attr('markerUnits', 'strokeWidth') + .attr('orient', 'auto') + .attr('refX', 0) + .attr('refY', 0) + .attr('viewBox', '-1 0 2 5') + .append('path') + .attr('d', 'M 0,0 m -1,0 L 1,0 L 1,5 L -1,5 Z'); this._linesBase = this._svgOverlay.append('g').attr('class', 'base'); this._linesBase.selectAll('line').data(this._geometry.lines); @@ -395,12 +410,12 @@ export default class MeasureTool { : getClass('cover-circle', this._options.invertColor) ) .attr('r', nodeTargetRadius) - .attr('cx', ([d]) => this._projectionUtility.latLngToSvgPoint(d)[0]) - .attr('cy', ([d]) => this._projectionUtility.latLngToSvgPoint(d)[1]) - .on('mouseover', function (event, [d, i]) { + .attr('cx', ([d]) => this._projectionUtility.lngLatToSvgPoint(d)[0]) + .attr('cy', ([d]) => this._projectionUtility.lngLatToSvgPoint(d)[1]) + .on('mouseover', function(event, [d, i]) { self._onOverCircle(d, i, this); }) - .on('mouseout', function (event, [d, i]) { + .on('mouseout', function(event, [d, i]) { self._onOutCircle(d, i, this); }) .on('mousedown', () => this._hideTooltip()); @@ -411,12 +426,12 @@ export default class MeasureTool { .append('circle') .attr('class', getClass('cover-circle', this._options.invertColor)) .attr('r', nodeTargetRadius) - .attr('cx', ([d]) => this._projectionUtility.latLngToSvgPoint(d)[0]) - .attr('cy', ([d]) => this._projectionUtility.latLngToSvgPoint(d)[1]) - .on('mouseover', function (event, [d, i]) { + .attr('cx', ([d]) => this._projectionUtility.lngLatToSvgPoint(d)[0]) + .attr('cy', ([d]) => this._projectionUtility.lngLatToSvgPoint(d)[1]) + .on('mouseover', function(event, [d, i]) { self._onOverCircle(d, i, this); }) - .on('mouseout', function (event, [d, i]) { + .on('mouseout', function(event, [d, i]) { self._onOutCircle(d, i, this); }) .on('mousedown', () => this._hideTooltip()); @@ -438,19 +453,19 @@ export default class MeasureTool { : getClass('touch-circle', this._options.invertColor) ) .attr('r', touchTargetRadius) - .attr('cx', ([d]) => this._projectionUtility.latLngToSvgPoint(d)[0]) - .attr('cy', ([d]) => this._projectionUtility.latLngToSvgPoint(d)[1]) - .on('mouseover', function (event, [d, i]) { + .attr('cx', ([d]) => this._projectionUtility.lngLatToSvgPoint(d)[0]) + .attr('cy', ([d]) => this._projectionUtility.lngLatToSvgPoint(d)[1]) + .on('mouseover', function(event, [d, i]) { self._onOverCircle(d, i, this); }) - .on('mouseout', function (event, [d, i]) { + .on('mouseout', function(event, [d, i]) { self._onOutCircle(d, i, this); }) - .on('touchstart', function (event, [d, i]) { + .on('touchstart', function(event, [d, i]) { event.preventDefault(); self._onOverCircle(d, i, this, true); }) - .on('touchend', function (event, [d, i]) { + .on('touchend', function(event, [d, i]) { event.preventDefault(); self._onOutCircle(d, i, this); }) @@ -463,19 +478,19 @@ export default class MeasureTool { .append('circle') .attr('class', getClass('touch-circle', this._options.invertColor)) .attr('r', touchTargetRadius) - .attr('cx', ([d]) => this._projectionUtility.latLngToSvgPoint(d)[0]) - .attr('cy', ([d]) => this._projectionUtility.latLngToSvgPoint(d)[1]) - .on('mouseover', function (event, [d, i]) { + .attr('cx', ([d]) => this._projectionUtility.lngLatToSvgPoint(d)[0]) + .attr('cy', ([d]) => this._projectionUtility.lngLatToSvgPoint(d)[1]) + .on('mouseover', function(event, [d, i]) { self._onOverCircle(d, i, this); }) - .on('mouseout', function (event, [d, i]) { + .on('mouseout', function(event, [d, i]) { self._onOutCircle(d, i, this); }) - .on('touchstart', function (event, [d, i]) { + .on('touchstart', function(event, [d, i]) { event.preventDefault(); self._onOverCircle(d, i, this, true); }) - .on('touchend', function (event, [d, i]) { + .on('touchend', function(event, [d, i]) { event.preventDefault(); self._onOutCircle(d, i, this); }) @@ -492,10 +507,10 @@ export default class MeasureTool { .selectAll('line') .data(this._geometry.lines) .attr('class', getClass('base-line', this._options.invertColor)) - .attr('x1', (d) => this._projectionUtility.latLngToSvgPoint(d[0])[0]) - .attr('y1', (d) => this._projectionUtility.latLngToSvgPoint(d[0])[1]) - .attr('x2', (d) => this._projectionUtility.latLngToSvgPoint(d[1])[0]) - .attr('y2', (d) => this._projectionUtility.latLngToSvgPoint(d[1])[1]) + .attr('x1', (d) => this._projectionUtility.lngLatToSvgPoint(d[0])[0]) + .attr('y1', (d) => this._projectionUtility.lngLatToSvgPoint(d[0])[1]) + .attr('x2', (d) => this._projectionUtility.lngLatToSvgPoint(d[1])[0]) + .attr('y2', (d) => this._projectionUtility.lngLatToSvgPoint(d[1])[1]) .each((d) => this._updateSegment(d)); linesBase.exit().remove(); @@ -503,10 +518,10 @@ export default class MeasureTool { .enter() .append('line') .attr('class', getClass('base-line', this._options.invertColor)) - .attr('x1', (d) => this._projectionUtility.latLngToSvgPoint(d[0])[0]) - .attr('y1', (d) => this._projectionUtility.latLngToSvgPoint(d[0])[1]) - .attr('x2', (d) => this._projectionUtility.latLngToSvgPoint(d[1])[0]) - .attr('y2', (d) => this._projectionUtility.latLngToSvgPoint(d[1])[1]) + .attr('x1', (d) => this._projectionUtility.lngLatToSvgPoint(d[0])[0]) + .attr('y1', (d) => this._projectionUtility.lngLatToSvgPoint(d[0])[1]) + .attr('x2', (d) => this._projectionUtility.lngLatToSvgPoint(d[1])[0]) + .attr('y2', (d) => this._projectionUtility.lngLatToSvgPoint(d[1])[1]) .each((d) => this._updateSegment(d)); let linesAux = this._linesAux @@ -515,17 +530,17 @@ export default class MeasureTool { .join('line') .datum((d, i) => [d, i]) .attr('class', 'aux-line') - .attr('x1', ([d]) => this._projectionUtility.latLngToSvgPoint(d[0])[0]) - .attr('y1', ([d]) => this._projectionUtility.latLngToSvgPoint(d[0])[1]) - .attr('x2', ([d]) => this._projectionUtility.latLngToSvgPoint(d[1])[0]) - .attr('y2', ([d]) => this._projectionUtility.latLngToSvgPoint(d[1])[1]); + .attr('x1', ([d]) => this._projectionUtility.lngLatToSvgPoint(d[0])[0]) + .attr('y1', ([d]) => this._projectionUtility.lngLatToSvgPoint(d[0])[1]) + .attr('x2', ([d]) => this._projectionUtility.lngLatToSvgPoint(d[1])[0]) + .attr('y2', ([d]) => this._projectionUtility.lngLatToSvgPoint(d[1])[1]); linesAux .on('mousemove', (event, [d]) => { let point = Helper.findTouchPoint( [ - this._projectionUtility.latLngToSvgPoint(d[0]), - this._projectionUtility.latLngToSvgPoint(d[1]), + this._projectionUtility.lngLatToSvgPoint(d[0]), + this._projectionUtility.lngLatToSvgPoint(d[1]) ], [event.offsetX, event.offsetY] ); @@ -546,10 +561,10 @@ export default class MeasureTool { .join('line') .datum((d, i) => [d, i]) .attr('class', 'aux-line') - .attr('x1', ([d]) => this._projectionUtility.latLngToSvgPoint(d[0])[0]) - .attr('y1', ([d]) => this._projectionUtility.latLngToSvgPoint(d[0])[1]) - .attr('x2', ([d]) => this._projectionUtility.latLngToSvgPoint(d[1])[0]) - .attr('y2', ([d]) => this._projectionUtility.latLngToSvgPoint(d[1])[1]); + .attr('x1', ([d]) => this._projectionUtility.lngLatToSvgPoint(d[0])[0]) + .attr('y1', ([d]) => this._projectionUtility.lngLatToSvgPoint(d[0])[1]) + .attr('x2', ([d]) => this._projectionUtility.lngLatToSvgPoint(d[1])[0]) + .attr('y2', ([d]) => this._projectionUtility.lngLatToSvgPoint(d[1])[1]); const lineDrag = this._lineDrag.selectAll('line').data([]); @@ -560,27 +575,43 @@ export default class MeasureTool { const { paths, gmPath } = this._getProjectedPath(); const tickPath = this._tickPath.selectAll('path') .data(paths) - .attr("class", "tick-path") + .attr('class', 'tick-path') .attr('d', gmPath); + tickPath.exit().remove(); - tickPath.enter() + tickPath + .enter() .append('path') - .attr('d', gmPath); + .attr('d', gmPath) + .attr('marker-start', `url(#marker-small-ticks)`) + .attr('marker-mid', `url(#marker-small-ticks)`) + .attr('marker-end', `url(#marker-small-ticks)`); } _getProjectedPath() { const self = this; this._computeTickLength(); + // FIXME - interpolate every points along a line that span across the world + // when the zoom level is large kills the performance. Need a different way + // of to partial interpolate the points that only within the bounds. + // todo - check if d3 projection handles geodesic projection for start/end const points = this._helper.interpolatePointsOnPath( this._geometry.lines, this._tickLength, true ); + console.debug('points count', points.length); const paths = [Geometry.toLineString(points)]; const gmTransform = geoTransform({ - point: function(lat, lng) { - const [x, y] = self._projectionUtility.latLngToSvgPoint([lat, lng]); - this.stream.point(x, y); + point: function(lng, lat) { + if (self._map.getBounds().contains({ lat, lng })) { + const [x, y] = self._projectionUtility.lngLatToSvgPoint([lng, lat]); + this.stream.point(x, y); + } } }); + // const { east, north, south, west } = this._map.getBounds().toJSON(); + // const [x0, y0] = this._projectionUtility.latLngToSvgPoint([south, west]); + // const [x1, y1] = this._projectionUtility.latLngToSvgPoint([north, east]); + // const clip = geoClipRectangle(x0, y0, x1, y1); const gmPath = geoPath().projection(gmTransform); return { paths, gmPath }; } @@ -596,8 +627,8 @@ export default class MeasureTool { .attr('text-anchor', 'middle') .attr('dominant-baseline', 'text-before-edge') .attr('transform', (d) => { - let p1 = this._projectionUtility.latLngToSvgPoint(d[0]); - let p2 = this._projectionUtility.latLngToSvgPoint(d[1]); + let p1 = this._projectionUtility.lngLatToSvgPoint(d[0]); + let p2 = this._projectionUtility.lngLatToSvgPoint(d[1]); return Helper.transformText(p1, p2); }) .text((d, i) => @@ -614,8 +645,8 @@ export default class MeasureTool { .attr('text-anchor', 'middle') .attr('dominant-baseline', 'text-before-edge') .attr('transform', (d) => { - let p1 = this._projectionUtility.latLngToSvgPoint(d[0]); - let p2 = this._projectionUtility.latLngToSvgPoint(d[1]); + let p1 = this._projectionUtility.lngLatToSvgPoint(d[0]); + let p2 = this._projectionUtility.lngLatToSvgPoint(d[1]); return Helper.transformText(p1, p2); }) .text((d, i) => @@ -632,14 +663,14 @@ export default class MeasureTool { .attr('class', (d, i) => i === 0 ? `${getClass( - 'node-measure-text', - this._options.invertColor - )} head-text` + 'node-measure-text', + this._options.invertColor + )} head-text` : getClass('node-measure-text', this._options.invertColor) ) .attr('text-anchor', 'middle') .attr('dominant-baseline', 'text-after-edge') - .attr('x', (d) => this._projectionUtility.latLngToSvgPoint(d)[0]) + .attr('x', (d) => this._projectionUtility.lngLatToSvgPoint(d)[0]) .attr('y', this._transformNodeTextY.bind(this)) .text((d, i) => { let len = this._helper.computePathLength( @@ -657,14 +688,14 @@ export default class MeasureTool { .attr('class', (d, i) => i === 0 ? `${getClass( - 'node-measure-text', - this._options.invertColor - )} head-text` + 'node-measure-text', + this._options.invertColor + )} head-text` : getClass('node-measure-text', this._options.invertColor) ) .attr('text-anchor', 'middle') .attr('dominant-baseline', 'text-after-edge') - .attr('x', (d) => this._projectionUtility.latLngToSvgPoint(d)[0]) + .attr('x', (d) => this._projectionUtility.lngLatToSvgPoint(d)[0]) .attr('y', this._transformNodeTextY.bind(this)) .text((d, i) => { let len = this._helper.computePathLength( @@ -689,7 +720,7 @@ export default class MeasureTool { if (this._options.tooltip && !isTouch) { this._tooltip.show( - this._projectionUtility.latLngToContainerPoint(d), + this._projectionUtility.lngLatToContainerPoint(d), i === 0 ? Config.tooltipText2(this._options.language) : Config.tooltipText1(this._options.language) @@ -710,7 +741,7 @@ export default class MeasureTool { let self = this; let isDragged = false; - let circleDrag = drag().on('drag', function (event, [, i]) { + let circleDrag = drag().on('drag', function(event, [, i]) { isDragged = true; self._dragging = true; @@ -730,13 +761,13 @@ export default class MeasureTool { ); }); - circleDrag.on('start', function (event) { + circleDrag.on('start', function(event) { event.sourceEvent.stopPropagation(); select(this).raise().attr('r', nodeTargetExpandRadius); self._disableMapScroll(); }); - circleDrag.on('end', function (event, [d, i]) { + circleDrag.on('end', function(event, [d, i]) { self._enableMapScroll(); if (!isDragged) { if (i > 0) { @@ -789,10 +820,10 @@ export default class MeasureTool { .enter() .append('line') .attr('class', getClass('base-line', this._options.invertColor)) - .attr('x1', (d) => this._projectionUtility.latLngToSvgPoint(d[0])[0]) - .attr('y1', (d) => this._projectionUtility.latLngToSvgPoint(d[0])[1]) - .attr('x2', (d) => this._projectionUtility.latLngToSvgPoint(d[1])[0]) - .attr('y2', (d) => this._projectionUtility.latLngToSvgPoint(d[1])[1]); + .attr('x1', (d) => this._projectionUtility.lngLatToSvgPoint(d[0])[0]) + .attr('y1', (d) => this._projectionUtility.lngLatToSvgPoint(d[0])[1]) + .attr('x2', (d) => this._projectionUtility.lngLatToSvgPoint(d[1])[0]) + .attr('y2', (d) => this._projectionUtility.lngLatToSvgPoint(d[1])[1]); this._linesBase.selectAll('line').style('display', 'none'); this._linesAux.selectAll('line').style('display', 'none'); @@ -877,7 +908,7 @@ export default class MeasureTool { .select(`text:nth-child(${index + 1})`) .attr('transform', (d) => { let p1 = [event.x, event.y]; - let p2 = this._projectionUtility.latLngToSvgPoint(d[1]); + let p2 = this._projectionUtility.lngLatToSvgPoint(d[1]); return Helper.transformText(p1, p2); }) .text((d) => @@ -893,7 +924,7 @@ export default class MeasureTool { this._segmentText .select(`text:nth-child(${index})`) .attr('transform', (d) => { - let p1 = this._projectionUtility.latLngToSvgPoint(d[0]); + let p1 = this._projectionUtility.lngLatToSvgPoint(d[0]); let p2 = [event.x, event.y]; return Helper.transformText(p1, p2); }) @@ -916,7 +947,7 @@ export default class MeasureTool { let offset; if ( index > 0 && - this._projectionUtility.latLngToSvgPoint( + this._projectionUtility.lngLatToSvgPoint( this._geometry.nodes[index - 1] )[1] < event.y ) { @@ -930,13 +961,13 @@ export default class MeasureTool { let offset; if ( index + 1 > 0 && - event.y < this._projectionUtility.latLngToSvgPoint(d)[1] + event.y < this._projectionUtility.lngLatToSvgPoint(d)[1] ) { offset = 23; } else { offset = -7; } - return this._projectionUtility.latLngToSvgPoint(d)[1] + offset; + return this._projectionUtility.lngLatToSvgPoint(d)[1] + offset; }); let followingNodes = this._nodeText .selectAll('text') @@ -945,7 +976,7 @@ export default class MeasureTool { let len = this._helper.computePathLength([ ...this._geometry.nodes.slice(0, index), this._projectionUtility.svgPointToLatLng([event.x, event.y]), - ...this._geometry.nodes.slice(index + 1, index + 1 + i), + ...this._geometry.nodes.slice(index + 1, index + 1 + i) ]); if (index + i === this._geometry.nodes.length - 1) { this._length = len; @@ -978,12 +1009,12 @@ export default class MeasureTool { _disableMapScroll() { this._zoomControl = !!document.querySelector( - "button[aria-label='Zoom in']" + 'button[aria-label=\'Zoom in\']' ); this._map.setOptions({ scrollwheel: false, gestureHandling: 'none', - zoomControl: false, + zoomControl: false }); } @@ -991,7 +1022,7 @@ export default class MeasureTool { this._map.setOptions({ scrollwheel: true, gestureHandling: 'auto', - zoomControl: this._zoomControl, + zoomControl: this._zoomControl }); } @@ -1002,7 +1033,7 @@ export default class MeasureTool { } else { offset = -7; } - return this._projectionUtility.latLngToSvgPoint(d)[1] + offset; + return this._projectionUtility.lngLatToSvgPoint(d)[1] + offset; } _updateArea(i, pointToCompare) { @@ -1021,9 +1052,9 @@ export default class MeasureTool { offset > tolerance ? 0 : this._helper.computeArea([ - pointToCompare, - ...this._geometry.nodes.slice(1, n - 1), - ]); + pointToCompare, + ...this._geometry.nodes.slice(1, n - 1) + ]); } else if (i === n - 1) { offset = this._helper.computeLengthBetween( pointToCompare, @@ -1042,10 +1073,10 @@ export default class MeasureTool { offset > tolerance ? 0 : this._helper.computeArea([ - ...this._geometry.nodes.slice(0, i), - pointToCompare, - ...this._geometry.nodes.slice(i + 1), - ]); + ...this._geometry.nodes.slice(0, i), + pointToCompare, + ...this._geometry.nodes.slice(i + 1) + ]); } else { offset = this._helper.computeLengthBetween( this._geometry.nodes[0], @@ -1115,14 +1146,15 @@ export default class MeasureTool { area: this.area, areaText: this.areaText, segments: this.segments, - points: this.points, - }, + points: this.points + } }; } _computeTickLength() { - const metersPerPixel = 156543.03392 * Math.cos(this._map.getCenter().lat() * Math.PI / 180) - / Math.pow(2, this._map.getZoom()); - this._tickLength = metersPerPixel * 30; + // const metersPerPixel = 156543.03392 * Math.cos(this._map.getCenter().lat() * Math.PI / 180) + // / Math.pow(2, this._map.getZoom()); + // this._tickLength = metersPerPixel * 30; + this._tickLength = 1600000 / Math.pow(2, this._map.getZoom() - 1); } } diff --git a/src/index.scss b/src/index.scss index 2a8f594..2cd6941 100644 --- a/src/index.scss +++ b/src/index.scss @@ -29,10 +29,19 @@ $alpha-black: rgba(0, 0, 0, 1); .tick-path { fill: none; stroke: red; - stroke-width: 6px; + stroke-width: 2.5px; pointer-events: none; } +.ticks { + pointer-events: none; + marker { + path { + fill: blue; + } + } +} + .base-line { fill: none; stroke: black; diff --git a/src/projection-utility.js b/src/projection-utility.js index 5f0c9fa..ac37d38 100644 --- a/src/projection-utility.js +++ b/src/projection-utility.js @@ -8,7 +8,7 @@ export default class ProjectionUtility { this._projection = projection; } - latLngToSvgPoint(coords) { + lngLatToSvgPoint(coords) { let rate = this._options.offsetRate / 2; let latLng = new google.maps.LatLng(coords[1], coords[0]); let svgPoint = this._projection.fromLatLngToDivPixel(latLng); @@ -29,7 +29,7 @@ export default class ProjectionUtility { ); } - latLngToContainerPoint(coords) { + lngLatToContainerPoint(coords) { return this._projection.fromLatLngToContainerPixel( new google.maps.LatLng(coords[1], coords[0]) ); From 08120b1221f559364e43317ab89aef852be3ca12 Mon Sep 17 00:00:00 2001 From: Zhenyang Hua Date: Sat, 16 Jan 2021 12:00:26 -0500 Subject: [PATCH 3/3] WIP --- rollup.config.js | 2 +- src/geometry.js | 4 ++++ src/index.js | 28 ++++++++++++++++------------ src/index.scss | 2 +- 4 files changed, 22 insertions(+), 14 deletions(-) diff --git a/rollup.config.js b/rollup.config.js index 932b74a..6f81ec4 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -46,4 +46,4 @@ const esm = { onwarn }; -export default [umd, esm]; +export default [esm]; diff --git a/src/geometry.js b/src/geometry.js index f146d18..7ae226f 100644 --- a/src/geometry.js +++ b/src/geometry.js @@ -12,6 +12,10 @@ export class Geometry { return segments; } + get lingString() { + return Geometry.toLineString(this._nodes); + } + static toLineString(points) { return { "type": "Feature", diff --git a/src/index.js b/src/index.js index e7d5dbc..ba9305f 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,6 @@ import { drag } from 'd3-drag'; import { select, selectAll } from 'd3-selection'; -import { geoPath, geoTransform, geoClipRectangle } from 'd3-geo'; +import { geoPath, geoTransform, geoClipRectangle, geoProjection, geoMercator } from 'd3-geo'; import { Config } from './config'; import ContextMenu from './context-menu'; import Tooltip from './tooltip'; @@ -582,10 +582,10 @@ export default class MeasureTool { tickPath .enter() .append('path') - .attr('d', gmPath) - .attr('marker-start', `url(#marker-small-ticks)`) - .attr('marker-mid', `url(#marker-small-ticks)`) - .attr('marker-end', `url(#marker-small-ticks)`); + .attr('d', gmPath); + // .attr('marker-start', `url(#marker-small-ticks)`) + // .attr('marker-mid', `url(#marker-small-ticks)`) + // .attr('marker-end', `url(#marker-small-ticks)`); } _getProjectedPath() { @@ -598,21 +598,25 @@ export default class MeasureTool { const points = this._helper.interpolatePointsOnPath( this._geometry.lines, this._tickLength, true ); - console.debug('points count', points.length); - const paths = [Geometry.toLineString(points)]; + // console.debug('points count', points.length); + // const paths = [Geometry.toLineString(points)]; + const paths = [this._geometry.lingString]; const gmTransform = geoTransform({ point: function(lng, lat) { - if (self._map.getBounds().contains({ lat, lng })) { + // if (self._map.getBounds().contains({ lat, lng })) { const [x, y] = self._projectionUtility.lngLatToSvgPoint([lng, lat]); this.stream.point(x, y); - } + // } } }); // const { east, north, south, west } = this._map.getBounds().toJSON(); - // const [x0, y0] = this._projectionUtility.latLngToSvgPoint([south, west]); - // const [x1, y1] = this._projectionUtility.latLngToSvgPoint([north, east]); + // const [x0, y0] = this._projectionUtility.lngLatToSvgPoint([south, west]); + // const [x1, y1] = this._projectionUtility.lngLatToSvgPoint([north, east]); // const clip = geoClipRectangle(x0, y0, x1, y1); - const gmPath = geoPath().projection(gmTransform); + const projection = geoMercator(); + // projection.postclip(clip); + const gmPath = geoPath(projection).projection(gmTransform); + return { paths, gmPath }; } diff --git a/src/index.scss b/src/index.scss index 2cd6941..1e8788a 100644 --- a/src/index.scss +++ b/src/index.scss @@ -29,7 +29,7 @@ $alpha-black: rgba(0, 0, 0, 1); .tick-path { fill: none; stroke: red; - stroke-width: 2.5px; + stroke-width: 6px; pointer-events: none; }