export function getBearingAndCoordinatesAlongLine ({ coordinates, spacingMeters = DIRECTION_MARKER_SPACING_METERS }) { const markers = [{ bearing: bearing(point(coordinates[0]), point(coordinates[1])), coordinates: [coordinates[0][0], coordinates[0][1]] }] for (let segIdx = 1, distanceToLastMarker = 0, totalDistance = 0; segIdx < coordinates.length; segIdx++) { const segStart = coordinates[segIdx - 1] const segEnd = coordinates[segIdx] const distanceThisSegment = distance(point(segStart), point(segEnd), 'kilometers') * 1000 // while not if, may need multiple markers on a single long segment while (distanceToLastMarker + spacingMeters < totalDistance + distanceThisSegment) { // total distance is at start of this segment, figure out how much of the spacing is in this segment vs. last const metersIntoThisSegment = distanceToLastMarker + spacingMeters - totalDistance const frac = metersIntoThisSegment / distanceThisSegment const lat = segStart[1] + (segEnd[1] - segStart[1]) * frac const lon = segStart[0] + (segEnd[0] - segStart[0]) * frac markers.push({ bearing: bearing(point(segStart), point(segEnd)), coordinates: [lon, lat] }) distanceToLastMarker += spacingMeters } totalDistance += distanceThisSegment } return markers }
function lineBuffer(line, radius, units, resolution) { var lineOffset = []; line.geometry.coordinates = removeDuplicates(line.geometry.coordinates); if (!(equalArrays(line.geometry.coordinates[0],line.geometry.coordinates[line.geometry.coordinates.length-1]))) { // situation at first point var firstLinePoint = helpers.point(line.geometry.coordinates[0]); var secondLinePoint = helpers.point(line.geometry.coordinates[1]); var firstLineBearing = bearing(firstLinePoint, secondLinePoint); var firstBufferPoint = destination(firstLinePoint, radius, firstLineBearing + 90, units); // situation at last point var lastLinePoint = helpers.point(line.geometry.coordinates[line.geometry.coordinates.length-1]); var secondlastLinePoint = helpers.point(line.geometry.coordinates[line.geometry.coordinates.length-2]); var lastLineBearing = bearing(lastLinePoint, secondlastLinePoint); lineOffset.push([]); lineOffset[0].push.apply(lineOffset[0],[firstBufferPoint.geometry.coordinates]); // Add first buffer point in order to close ring lineOffset[0].push.apply(lineOffset[0],lineOffsetOneSide(line, radius, units, resolution, false, true).geometry.coordinates); lineOffset[0].push.apply(lineOffset[0],arc(lastLinePoint, radius, lastLineBearing - 90, lastLineBearing + 90, units, resolution, true).geometry.coordinates); lineOffset[0].push.apply(lineOffset[0],lineOffsetOneSide(line, radius, units, resolution, true, true).geometry.coordinates); lineOffset[0].push.apply(lineOffset[0],arc(firstLinePoint, radius, firstLineBearing - 90, firstLineBearing + 90, units, resolution, true).geometry.coordinates); return offsetToBuffer(helpers.polygon(lineOffset)); } else { lineOffset.push(ringOffsetOneSide(line, radius, units, resolution, false, true).geometry.coordinates); lineOffset.push(ringOffsetOneSide(line, radius, units, resolution, true, true).geometry.coordinates); return offsetToBuffer(helpers.polygon(lineOffset)); } }
module.exports = function (from, to) { var dist = distance(from, to, 'miles'); var heading = bearing(from, to); var midpoint = destination(from, dist / 2, heading, 'miles'); return midpoint; };
module.exports = function (point1, point2) { var dist = distance(point1, point2, 'miles'); var heading = bearing(point1, point2); var midpoint = destination(point1, dist / 2, heading, 'miles'); return midpoint; };
function pointOnLine (pt, coords) { var units = 'miles' var closestPt = point([Infinity, Infinity], {dist: Infinity}); for(var i = 0; i < coords.length - 1; i++) { var start = point(coords[i]) var stop = point(coords[i+1]) //start start.properties.dist = distance(pt, start, units); //stop stop.properties.dist = distance(pt, stop, units); //perpendicular var direction = bearing(start, stop) var perpendicularPt = destination(pt, 1000 , direction + 90, units) // 1000 = gross var intersect = lineIntersects( pt.geometry.coordinates[0], pt.geometry.coordinates[1], perpendicularPt.geometry.coordinates[0], perpendicularPt.geometry.coordinates[1], start.geometry.coordinates[0], start.geometry.coordinates[1], stop.geometry.coordinates[0], stop.geometry.coordinates[1] ); if(!intersect) { perpendicularPt = destination(pt, 1000 , direction - 90, units) // 1000 = gross intersect = lineIntersects( pt.geometry.coordinates[0], pt.geometry.coordinates[1], perpendicularPt.geometry.coordinates[0], perpendicularPt.geometry.coordinates[1], start.geometry.coordinates[0], start.geometry.coordinates[1], stop.geometry.coordinates[0], stop.geometry.coordinates[1] ); } perpendicularPt.properties.dist = Infinity; var intersectPt; if(intersect) { var intersectPt = point(intersect); intersectPt.properties.dist = distance(pt, intersectPt, units); } if(start.properties.dist < closestPt.properties.dist) { closestPt = start; closestPt.properties.index = i; } if(stop.properties.dist < closestPt.properties.dist) { closestPt = stop; closestPt.properties.index = i; } if(intersectPt && intersectPt.properties.dist < closestPt.properties.dist){ closestPt = intersectPt; closestPt.properties.index = i; } } return closestPt; }
function lineOffsetOneSide(line, radius, units, resolution, reverse, right) { if (reverse === undefined) var reverse = false; if (right === undefined) var right = true; if (reverse) line.geometry.coordinates = line.geometry.coordinates.reverse(); var coords = line.geometry.coordinates; var lineOffset = []; if (coords.length == 2) return helpers.lineString(lineOffset) for (var i = 1; i < coords.length-1; i++) { var previousLinePoint = helpers.point(coords[i-1]); var currentLinePoint = helpers.point(coords[i]); var nextLinePoint = helpers.point(coords[i+1]); var previousLineBearing = bearing(currentLinePoint, previousLinePoint); var nextLineBearing = bearing(currentLinePoint, nextLinePoint); lineOffset.push.apply(lineOffset, arc(currentLinePoint, radius, previousLineBearing - Math.pow(-1, right + 1) * 90, nextLineBearing + Math.pow(-1, right + 1) * 90, units, resolution, right, true).geometry.coordinates); } return helpers.lineString(lineOffset) }
function ringOffsetOneSide(ring, radius, units, resolution, reverse, right) { if (reverse === undefined) var reverse = false; if (right === undefined) var right = true; if (reverse) ring.geometry.coordinates = ring.geometry.coordinates.reverse(); var coords = ring.geometry.coordinates; // ring is a lineString var ringOffset = []; // situation at current point = point 0 var previousRingPoint = helpers.point(coords[coords.length-2]); var currentRingPoint = helpers.point(coords[0]); var nextRingPoint = helpers.point(coords[1]); var nextRingBearing = bearing(currentRingPoint, nextRingPoint); var currentBufferPoint = destination(currentRingPoint, radius, nextRingBearing + 90, units); var previousRingBearing = bearing(currentRingPoint, previousRingPoint); ringOffset.push.apply(ringOffset, [currentBufferPoint.geometry.coordinates]); // Add first buffer point in order to close ring ringOffset.push.apply(ringOffset, lineOffsetOneSide(ring, radius, units, resolution, false, right).geometry.coordinates); ringOffset.push.apply(ringOffset, arc(currentRingPoint, radius, previousRingBearing - Math.pow(-1, right + 1) * 90, nextRingBearing + Math.pow(-1, right + 1) * 90, units, resolution, right, true).geometry.coordinates); return helpers.lineString(ringOffset) }
module.exports = function lineSliceAlong(line, startDist, stopDist, units) { var coords; var slice = []; if (line.type === 'Feature') coords = line.geometry.coordinates; else if (line.type === 'LineString') coords = line.coordinates; else throw new Error('input must be a LineString Feature or Geometry'); var travelled = 0; var overshot, direction, interpolated; for (var i = 0; i < coords.length; i++) { if (startDist >= travelled && i === coords.length - 1) break; else if (travelled > startDist && slice.length === 0) { overshot = startDist - travelled; if (!overshot) return slice.push(coords[i]); direction = bearing(coords[i], coords[i - 1]) - 180; interpolated = destination(coords[i], overshot, direction, units); slice.push(interpolated.geometry.coordinates); } if (travelled >= stopDist) { overshot = stopDist - travelled; if (!overshot) return slice.push(coords[i]); direction = bearing(coords[i], coords[i - 1]) - 180; interpolated = destination(coords[i], overshot, direction, units); slice.push(interpolated.geometry.coordinates); return lineString(slice); } if (travelled >= startDist) { slice.push(coords[i]); } travelled += distance(coords[i], coords[i + 1], units); } return lineString(coords[coords.length - 1]); };
function locatePoint(pt, coords, mCoord) { var units = 'miles'; var closestPt = point([Infinity, Infinity], { dist: Infinity }); for (var i = 0; i < coords.length - 1; i++) { var start = point(coords[i]); var stop = point(coords[i + 1]); //start start.properties.dist = distance(pt, start, units); //stop stop.properties.dist = distance(pt, stop, units); //perpendicular var direction = bearing(start, stop); var perpendicularPt = destination(pt, 1000, direction + 90, units); // 1000 = gross var intersect = lineIntersects( pt.geometry.coordinates[0], pt.geometry.coordinates[1], perpendicularPt.geometry.coordinates[0], perpendicularPt.geometry.coordinates[1], start.geometry.coordinates[0], start.geometry.coordinates[1], stop.geometry.coordinates[0], stop.geometry.coordinates[1] ); if (!intersect) { perpendicularPt = destination(pt, 1000, direction - 90, units); // 1000 = gross intersect = lineIntersects( pt.geometry.coordinates[0], pt.geometry.coordinates[1], perpendicularPt.geometry.coordinates[0], perpendicularPt.geometry.coordinates[1], start.geometry.coordinates[0], start.geometry.coordinates[1], stop.geometry.coordinates[0], stop.geometry.coordinates[1] ); } perpendicularPt.properties.dist = Infinity; var intersectPt; if (intersect) { var intersectPt = point(intersect); intersectPt.properties.dist = distance(pt, intersectPt, units); } if (start.properties.dist < closestPt.properties.dist) { closestPt = start; closestPt.properties.index = i; } if (stop.properties.dist < closestPt.properties.dist) { closestPt = stop; closestPt.properties.index = i; } if (intersectPt && intersectPt.properties.dist < closestPt.properties.dist) { closestPt = intersectPt; closestPt.properties.index = i; } } var measureValue = interpolateM(coords[closestPt.properties.index], coords[closestPt.properties.index+1],closestPt,mCoord); return event({ fromM:measureValue, offset:closestPt.properties.dist, properties:pt.properties || {} }); }
export default function getStops ({segments}) { if (segments.length === 0) return [] if (segments.length === 1 && segments[0].geometry.type === 'Point') { let coord = segments[0].geometry.coordinates return [{ stopId: segments[0].fromStopId, index: 0, lat: coord[1], lon: coord[0], bearing: false, // this will hide directional icon, which is what we want autoCreated: !segments[0].stopAtStart, distanceFromStart: 0 }] } let ret = [] let coord = segments[0].geometry.coordinates[0] ret.push({ stopId: segments[0].fromStopId, index: 0, lat: coord[1], lon: coord[0], bearing: bearing(point(coord), point(segments[0].geometry.coordinates[1])), autoCreated: !segments[0].stopAtStart, distanceFromStart: 0 }) // loop over the route, making stops as we go let distanceToLastStop = 0 let distanceToLineSegmentStart = 0 for (let segIdx = 0; segIdx < segments.length; segIdx++) { const segment = segments[segIdx] // now loop over line segments within this segment, accumulating distance as we go // a single transit segment can have multiple line segments, because we've used a street router between endpoints for (let i = 1; i < segment.geometry.coordinates.length; i++) { const c0 = segment.geometry.coordinates[i - 1] const c1 = segment.geometry.coordinates[i] const distanceThisLineSegment = distance(point(c0), point(c1), 'kilometers') * 1000 // segment.spacing = 0 means no automatic stop creation in this segment while (segment.spacing > 0 && distanceToLastStop + segment.spacing < distanceToLineSegmentStart + distanceThisLineSegment) { // how far into the segment do we place the stop let frac = (distanceToLastStop + segment.spacing - distanceToLineSegmentStart) / distanceThisLineSegment if (frac < 0) frac = 0 // most likely the last segment did not have automatic stop creation let pos = [c0[0] + (c1[0] - c0[0]) * frac, c0[1] + (c1[1] - c0[1]) * frac] // can't just add segment.spacing because of converting negative fractions to zero above // this can happen when the last segment did not have automatic stop creation, or had a larger spacing // TODO in the latter case, we probably want to continue to apply the spacing from the last line segment until we create a new stop? distanceToLastStop = distanceToLineSegmentStart + frac * distanceThisLineSegment ret.push({ stopId: null, index: segIdx, lat: pos[1], lon: pos[0], autoCreated: true, bearing: bearing(point(c0), point(c1)), distanceFromStart: distanceToLastStop }) } distanceToLineSegmentStart += distanceThisLineSegment } if (segment.stopAtEnd) { let endCoord = segment.geometry.coordinates.slice(-1)[0] ret.push({ stopId: segment.toStopId, index: segIdx + 1, lat: endCoord[1], lon: endCoord[0], autoCreated: false, distanceFromStart: distanceToLineSegmentStart, bearing: segIdx < segments.length - 1 ? bearing(point(segments[segIdx + 1].geometry.coordinates[0]), point(segments[segIdx + 1].geometry.coordinates[1])) : false }) // restart the spacing distanceToLastStop = distanceToLineSegmentStart // distanceToLineSegmentStart already set to the start of the next line segment } } // filter out autocreated stops that are very close to bona fide stops ret = ret.filter((stop, stopIndex, stops) => { if (!stop.autoCreated) return true let spacingThisSegment = segments[stop.index].spacing if (spacingThisSegment === 0) { // fail harder? debug(`segment ${stop.index} contains auto-created stops but does not have automatic stop creation enabled?`) return true } // last stop if (stopIndex === stops.length - 1) return true // only need to check if it's too close to the next stop, as it will always be spaced away from the previous // stop, since we flow stops forward from the bona fide stops let nextStop = stops[stopIndex + 1] let delta = nextStop.distanceFromStart - stop.distanceFromStart if (!nextStop.autoCreated && delta / spacingThisSegment < MIN_SPACING_PERCENTAGE) return false else return true }) return ret }