<!DOCTYPE html>
<html>
<head>
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.40.1/mapbox-gl.css' rel='stylesheet' />
<link rel="stylesheet" href="style.css">
<script src="https://api.tiles.mapbox.com/mapbox-gl-js/v0.40.1/mapbox-gl.js"></script>
<script src="https://npmcdn.com/@turf/turf/turf.js"></script>
</head>
<body>
<div id="map"></div>
<script src="script.js"></script>
</body>
</html>
var map = new mapboxgl.Map({
container: 'map',
style: {
"version": 8,
"sources": {
"osm2vectortiles": {
"type": "vector",
"url": "https://osm2vectortiles.tileserver.com/v2.json"
},
},
"layers": [
{
"id": "background",
"type": "background",
"paint": {
"background-color": "#41afa5"
}
},
{
"id": "water",
"type": "fill",
"source": "osm2vectortiles",
"source-layer": "water",
"filter": ["==", "$type", "Polygon"],
"paint": {
"fill-color": "#3887be"
}
}
],
},
center: [-96, 37.8],
zoom: 2,
interactive: true
});
map.on('style.load', function (e) {
map.addSource('markers', {
"type": "geojson",
"data": {
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [-77, 39]
},
"properties": {
"title": "A",
"marker-symbol": "default_marker"
}
}, {
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [-122, 38]
},
"properties": {
"title": "B",
"marker-color": "#ff00ff",
"marker-symbol": "secondary_marker"
}
}]
}
});
map.addLayer({
"id": "markers",
"source": "markers",
"type": "circle",
"paint": {
"circle-radius": 10,
"circle-color": "#007cbf"
}
});
let popup = new mapboxgl.Popup({
closeButton: false,
closeOnClick: false
});
map.on("mouseenter", "markers", e => {
map.getCanvas().style.cursor = "pointer";
popup
.setLngLat(map.unproject(e.point))
.setHTML("<h3>" + e.features[0].properties.title + "</h3>")
.addTo(map);
});
map.on("mouseleave", "markers", () => {
map.getCanvas().style.cursor = "";
popup.remove();
});
let line = {
"type": "LineString",
"coordinates": [
[-77, 39],
[-90, 50],
[-122, 38],
],
};
map.addLayer({
"id": "line",
"source": {
"type": "geojson",
"data": {
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"geometry": line,
}],
},
},
"type": "line",
"paint": {
"line-color": "yellow",
}
});
var getDirectDistance = function (point1, point2) {
let long1 = point1[0];
let long2 = point2[0];
let lat1 = point1[1];
let lat2 = point2[1];
let longDiff = long2 - long1;
let latDiff = lat2 - lat1;
return Math.sqrt(Math.pow(longDiff, 2) + Math.pow(latDiff, 2));
};
var getPointAndAngleAlongLineString = function (lineString, percentage) {
let coords;
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');
}
if (coords.length < 2) {
throw new Error('input must be a at least two points');
}
if (percentage < 0 || percentage > 1) {
throw new Error("percentage should be between 0 and 1");
}
let distances = [];
let totalDistance = 0;
for (let i = 0; i < coords.length - 1; i++) {
let dist = getDirectDistance(coords[i], coords[i + 1]);
totalDistance += dist;
distances.push([dist, totalDistance, [coords[i], coords[i + 1]]]);
}
let distForPoint = totalDistance * percentage;
let segmentInfo = distForPoint === 0 ? distances[0] : distances.find(el => distForPoint > el[1] - el[0] && distForPoint <= el[1]);
if (segmentInfo) {
let dist = segmentInfo[0];
let distPtFromSegStart = distForPoint - segmentInfo[1] + segmentInfo[0];
let segmentStart = segmentInfo[2][0];
let startLong = segmentStart[0];
let startLat = segmentStart[1];
let segmentEnd = segmentInfo[2][1];
let endLong = segmentEnd[0];
let endLat = segmentEnd[1];
let longSign = endLong >= startLong ? 1 : -1;
let latSign = endLat >= startLat ? 1 : -1;
let pointLong = distPtFromSegStart * (endLong - startLong) / dist + startLong;
let pointLat = distPtFromSegStart * (endLat - startLat) / dist + startLat;
let angle = Math.acos((endLat - startLat) / dist);
return {
point: turf.point([pointLong, pointLat]),
latAngle: angle,
longSign: longSign,
latSign: latSign,
};
} else {
return null;
}
};
var getPointFromAnotherPointWithDistanceAngleAndDirections = function (refPoint, distance, latAngle, longSign, latSign) {
let refCoords = turf.getCoord(refPoint);
let diffLong = distance * Math.sin(latAngle);
let diffLat = distance * Math.cos(latAngle);
return turf.point([
refCoords[0] + longSign * diffLong,
refCoords[1] + latSign * diffLat,
]);
}
let pointInfo = getPointAndAngleAlongLineString(line, 0.5);
let centerPoint = pointInfo.point;
let firstPoint = getPointFromAnotherPointWithDistanceAngleAndDirections(
centerPoint,
2,
pointInfo.latAngle + Math.PI / 8,
pointInfo.longSign,
pointInfo.latSign
);
let lastPoint = getPointFromAnotherPointWithDistanceAngleAndDirections(
centerPoint,
2,
- pointInfo.latAngle + Math.PI / 8,
pointInfo.longSign,
pointInfo.latSign
);
let directionLine = {
"type": "LineString",
"coordinates": [
turf.getCoord(firstPoint),
turf.getCoord(centerPoint),
turf.getCoord(lastPoint),
],
};
map.addLayer({
"id": "direction",
"source": {
"type": "geojson",
"data": {
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"geometry": directionLine,
}],
},
},
"type": "line",
"paint": {
"line-color": "red",
},
});
map.addLayer({
"id": "centerPoint",
"source": {
"type": "geojson",
"data": {
"type": "FeatureCollection",
"features": [centerPoint],
},
},
"type": "circle",
"paint": {
"circle-radius": 5,
"circle-color": "magenta",
},
})
});
body {
margin:0;
padding:0;
}
#map {
position:absolute;
top:0;
bottom:0;
width:100%;
}