Skip to content

Instantly share code, notes, and snippets.

@dnomadb
Created March 23, 2018 00:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dnomadb/4255f780da50bd982ed1b2eecb7b4a5c to your computer and use it in GitHub Desktop.
Save dnomadb/4255f780da50bd982ed1b2eecb7b4a5c to your computer and use it in GitHub Desktop.
var offsetPoint = function(p1,a,d){
var brng = a*(Math.PI/180.0);
var R = 41807040;
var lat1 = (Math.PI/180.0)*p1.lat;
var lon1 = (Math.PI/180.0)*p1.lng;
var lat2 = Math.asin( Math.sin(lat1)*Math.cos(d/R) +
Math.cos(lat1)*Math.sin(d/R)*Math.cos(brng));
var lon2 = lon1 + Math.atan2(Math.sin(brng)*Math.sin(d/R)*Math.cos(lat1),
Math.cos(d/R)-Math.sin(lat1)*Math.sin(lat2));
return {"lat":lat2*(180.0/Math.PI),"lng":lon2*(180.0/Math.PI)}
}
var bearingDegrees = function(p1,p2){
var dLon = (Math.PI/180.0)*((p2.lng-p1.lng));
var lat1 = (Math.PI/180.0)*p1.lat;
var lat2 = (Math.PI/180.0)*p2.lat;
var y = Math.sin(dLon) * Math.cos(lat2);
var x = Math.cos(lat1)*Math.sin(lat2) - Math.sin(lat1)*Math.cos(lat2)*Math.cos(dLon);
var brng = Math.atan2(y, x)*(180.0/Math.PI);
return brng;
}
var bearingRadians = function(p1,p2){
var dLon = (Math.PI/180.0)*((p2.lng-p1.lng));
var lat1 = (Math.PI/180.0)*p1.lat;
var lat2 = (Math.PI/180.0)*p2.lat;
var y = Math.sin(dLon) * Math.cos(lat2);
var x = Math.cos(lat1)*Math.sin(lat2) - Math.sin(lat1)*Math.cos(lat2)*Math.cos(dLon);
var brng = Math.atan2(y, x);
return brng;
}
var latLng2tile = function(lat,lon,zoom){
var eLng = (lon+180)/360*Math.pow(2,zoom);
var eLat = (1-Math.log(Math.tan(lat*Math.PI/180) + 1/Math.cos(lat*Math.PI/180))/Math.PI)/2 *Math.pow(2,zoom);
//x coord in image tile of lat/lng
var xInd = Math.round((eLng-Math.floor(eLng))*256);
//y coord in image tile of lat/lng
var yInd = Math.round((eLat-Math.floor(eLat))*256);
//flattened index for clamped array in imagedata
var fInd = yInd*256+xInd;
//for calling tile from array
var eLng = Math.floor(eLng);
var eLat = Math.floor(eLat);
return {"tileCall":""+zoom+"/"+eLng+"/"+eLat,"tileX":eLng,"tileY":eLat,"pX":xInd,"pY":yInd,"arrInd":fInd}
}
function drawChart(divID){
var divH = document.getElementById(divID).clientHeight,
divW = document.getElementById(divID).clientWidth,
chartSRC = '<iframe src="chart.html" id="chartframe" frameborder=0 height="'+divH+'" width ="'+divW+'" scrolling="no"></iframe>';
document.getElementById(divID).innerHTML = chartSRC;
}
function distance(p1,p2){
var R = 41807040;
var dLat = (Math.PI/180.0)*((p2.lat-p1.lat));
var dLon = (Math.PI/180.0)*((p2.lng-p1.lng));
var lat1 = (Math.PI/180.0)*p1.lat,
lat2 = (Math.PI/180.0)*p2.lat;
var a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.sin(dLon/2) * Math.sin(dLon/2) * Math.cos(lat1) * Math.cos(lat2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
var d = R * c;
return d;
}
<!DOCTYPE html>
<html>
<head>
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
<title>Sweep Snap</title>
<style type="text/css">
html { height: 100%; width: 100%}
body {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
}
#map_canvas {
width: 100%;
height: 100%;
}
</style>
<link href='https://www.mapbox.com/base/latest/base.css' rel='stylesheet'>
<script src='https://api.tiles.mapbox.com/mapbox.js/v1.6.3/mapbox.js'></script>
<link href='https://api.tiles.mapbox.com/mapbox.js/v1.6.3/mapbox.css' rel='stylesheet' />
</head>
<body>
<div id="map_canvas"></div>
<script type="text/javascript" src="general-geo-utils.js"></script>
<script type="text/javascript" src="path-edge-detect.js"></script>
<!--<script type="text/javascript" src="sweep-search.js"></script>worker: have to reference it in development for it to not cache-->
</body>
</html>
var tileSize,
locationHash,
everyOther = true;
var loadHash = window.location.hash.replace('#','').split('/');
if (loadHash.length != 3){
loadHash = [37.77872,-122.26403,18];
history.pushState(null,null,'#'+loadHash[0]+'/'+loadHash[1]+'/'+loadHash[2])
}
var map = L.map('map_canvas',{
center: [loadHash[0], loadHash[1]],
zoom: loadHash[2],
worldCopyJump: true,
doubleClickZoom: false
});
var tiles = new L.TileLayer.Canvas({
unloadInvisibleTiles:true,
attribution: '<a href="http://www.mapbox.com/about/maps/" target="_blank">Terms &amp; Feedback</a>'
});
var worker = new Worker('sweep-search.js');
worker.addEventListener('message', function(response) {
// Take the 'edge' response, set the cursor
//cursMark.setLatLng(response.data);
}, false);
var drawStyles = {
color: 'red'
}
var hoverStyle = {
color: 'white'
}
var lineArr = [L.polyline([],drawStyles)],
startPoint = L.circleMarker([],drawStyles)
.on('mouseover',function() {
this.setRadius(10)
.setStyle(hoverStyle);
})
.on('mouseout',function() {
this.setRadius(8)
.setStyle(drawStyles);
})
.on("click", endDraw),
currPoint = L.circleMarker([],drawStyles);
lineArr[0].addTo(map);
function drawShape(e){
lineArr[lineArr.length-1].addLatLng(e.latlng);
worker.postMessage({
'data':{
'lat': e.latlng.lat,
'lng': e.latlng.lng
},
type: 'firstpoint'
});
currPoint.setStyle(hoverStyle);
setTimeout(function(){
currPoint.setStyle(drawStyles);
},100)
}
var edgePoints = L.layerGroup([])
.addTo(map);
worker.addEventListener('message', function(response) {
// TODO - Edge correlation sensitive to first mouse move after click - need to work out a method to straighten.
if (response.data.type == "movepoint"){
lineArr[lineArr.length-1].spliceLatLngs(-1,1,response.data.data);
currPoint.setLatLng(response.data.data);
}
else if (response.data.type == "edgepoint"){
edgePoints.addLayer(L.circleMarker(response.data.data,{radius:3}));
}
else {
edgePoints.clearLayers();
}
}, false);
function updatePoint(e){
worker.postMessage({
'data':{
'lat': e.latlng.lat,
'lng': e.latlng.lng,
'zoom': map.getZoom()
},
type: 'mouseevent'
});
}
function startDraw(e) {
lineArr[lineArr.length-1].addTo(map)
drawShape(e);
map.on("mousemove", updatePoint);
currPoint
.setLatLng(e.latlng)
.setRadius(5)
.addTo(map);
startPoint
.setLatLng(e.latlng)
.setRadius(10)
.addTo(map);
}
function endDraw() {
lineArr[lineArr.length-1].spliceLatLngs(-1,1,startPoint.getLatLng());
map.removeLayer(startPoint);
map.removeLayer(currPoint);
lineArr.push(L.polyline([],drawStyles));
map.off('mousemove');
map.once("click", startDraw);
}
map.once("click", startDraw)
map.on("click", drawShape);
map.on('moveend',function(){
// Set url with lat/lng/zoom
locationHash = '#'+''+map.getCenter().toString().replace('LatLng(','').replace(')','').replace(', ','/')+'/'+map.getZoom();
history.replaceState(null,null,locationHash);
});
tiles.on('tileunload', function(e){
//Send tile unload data to worker to delete un-needed pixel data
worker.postMessage({'data':e.tile._tilePoint.id,'type':'tileunload'})
});
tiles.drawTile = function (canvas, tile, zoom) {
tileSize = this.options.tileSize;
var context = canvas.getContext('2d'),
imageObj = new Image(),
tileUID = ''+zoom+'/'+tile.x+'/'+tile.y;
// To access / delete tiles later
tile.id = tileUID;
imageObj.onload = function() {
// Draw Image Tile
context.drawImage(imageObj, 0, 0);
// Get Image Data
var imageData = context.getImageData(0, 0, tileSize, tileSize);
worker.postMessage({
'data':{
'tileUID':tileUID,
'tileSize':tileSize,
'array':imageData.data},
'type':'tiledata'},
[imageData.data.buffer]);
}
// Source of image tile
imageObj.crossOrigin = 'Anonymous';
imageObj.src = `https://b.tiles.mapbox.com/v4/mapbox.satellite/${zoom}/${tile.x}/${tile.y}.jpg?access_token=pk.eyJ1IjoibWF0dCIsImEiOiJTUHZkajU0In0.oB-OGTMFtpkga8vC48HjIg`
}
tiles.addTo(map);
importScripts('general-geo-utils.js');
// Variables to be defined later, but needed in this context
var firstPoint,
currBearing,
currZoom,
currDist,
newBearing,
lastMoveDist,
kernelSamples,
lastPoint;
// Tile Data Holder
var tileData = {};
//Interval (ft) upon which to sample
var sampInter = 4;
// Maximum Search Factor - search distance = sampInter*searchFactor;
var searchFactor = 24;
// Minimum mouse distane to re-search
var mouseMoveThreshold = 30;
// Minimum segment length before re-searching
var minimumEdgeLength = 70;
// Density on the line to sample
var searchDensity = 10;
//Listen for events
self.addEventListener('message', function(e) {
// obect to hold various methods based on message to worker
var edgeFind = {
// If event is a mouse event
mouseevent: function (inPoint) {
var currPoint = {
lat: inPoint.lat,
lng: inPoint.lng
}
currZoom = inPoint.zoom;
currDist = distance(currPoint,firstPoint);
kernelSamples = Math.round(currDist/searchDensity);
if (currDist<minimumEdgeLength) {
// send message to set point at mouse move position
self.postMessage({
type:'movepoint',
data:currPoint
});
lastMoveDist = mouseMoveThreshold+1;
lastPoint = currPoint;
}
else if (lastMoveDist>mouseMoveThreshold) {
//send message to clear edge points
self.postMessage({
type:'clearedgepoints'
});
currBearing = bearingDegrees(currPoint,firstPoint);
distInterval = currDist/(kernelSamples+1);
var lastSamplePoint = firstPoint,
accBearing = 0,
accX = 0,
accY = 0;
for (var k = 1;k<kernelSamples+1;k++) {
var tempSamplePt = offsetPoint(firstPoint,currBearing-180,k*distInterval),
alignKern = [0,0,0],
edgeAlignArr = [];
alignPositions = [];
for (var i = -1*searchFactor*sampInter;i<sampInter*searchFactor+1;i+=sampInter) {
var tempPoint = offsetPoint(tempSamplePt,currBearing+90,i),
tileInfo = latLng2tile(tempPoint.lat,tempPoint.lng,currZoom);
alignKern.pop();
alignKern.splice(0,0,tileData[tileInfo.tileCall][tileInfo.arrInd]);
if (i>=-1*searchFactor*sampInter+2*sampInter) {
edgeAlignArr.push(((searchFactor*sampInter)-Math.abs(i))*Math.abs(-1*alignKern[0]+1*alignKern[2]));
alignPositions.push(tempPoint);
}
}
var maxArrayIndex = edgeAlignArr.indexOf(Math.max.apply(null, edgeAlignArr));
if (maxArrayIndex!=-1) {
var currEdgeBearing = bearingRadians(lastSamplePoint,alignPositions[maxArrayIndex]);
accX+=Math.sin(currEdgeBearing);
accY+=Math.cos(currEdgeBearing);
lastSamplePoint = alignPositions[maxArrayIndex];
// Send message to make an "edge point"
self.postMessage({
type: 'edgepoint',
data: lastSamplePoint
});
}
}
newBearing = 180+(Math.atan2(accX,accY)*180/Math.PI);
lastPoint = offsetPoint(firstPoint,newBearing,-1*currDist);
lastMoveDist = 0;
}
else {
newPoint = offsetPoint(firstPoint,newBearing,-1*currDist);
// Corrected point at mean angle, and current distance
self.postMessage({
type: 'movepoint',
data: newPoint
});
lastMoveDist = distance(currPoint, lastPoint);
}
},
// If it is the first click on a segment
firstpoint: function (inPoint) {
firstPoint = {
lat: inPoint.lat,
lng: inPoint.lng
}
},
// If tile data was sent, add to data object
tiledata: function (inTile) {
var dataArray = new Uint16Array(65536);
for (var i=0;i<inTile.array.length/4;i++) {
// Only use R and G for speed
var tDataVal = (inTile.array[i*4]+inTile.array[i*4+1]);
dataArray[i] = (tDataVal);
}
delete inTile.array;
tileData[inTile.tileUID] = dataArray;
},
// If a tile unload event was sent, delete the corresponding data
tileunload: function (tileUnloadID) {
delete tileData[tileUnloadID];
}
}
// Call function based on message, send data.
edgeFind[e.data.type](e.data.data);
}, false);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment