Skip to content

Instantly share code, notes, and snippets.

@pgiraud
Last active August 29, 2015 14:13
Show Gist options
  • Select an option

  • Save pgiraud/b04834295af5fc250f2a to your computer and use it in GitHub Desktop.

Select an option

Save pgiraud/b04834295af5fc250f2a to your computer and use it in GitHub Desktop.
OpenLayers3 - Show destination
<!DOCTYPE html>
<html>
<head>
<title>OpenLayers3 Map Rotation with device compass</title>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width">
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="http://openlayers.org/en/master/css/ol.css" type="text/css">
<script src="//code.jquery.com/jquery-1.11.2.min.js"></script>
<script src="http://openlayers.org/en/master/build/ol.js"></script>
</head>
<body>
<div id="map"></div>
<button id="rotate">rotate</button>
</body>
<script src="script.js"></script>
<script src="intersections.js"></script>
</html>
// code adapted from
// http://martin-thoma.com/how-to-check-if-two-line-segments-intersect/
var EPSILON = 0.000001;
/**
* Calculate the cross product of two points.
* @param a first point
* @param b second point
* @return the value of the cross product
*/
function crossProduct(a, b) {
return a[0] * b[1] - b[0] * a[1];
}
/**
* Check if bounding boxes do intersect. If one bounding box
* touches the other, they do intersect.
* @param a first bounding box
* @param b second bounding box
* @return <code>true</code> if they intersect,
* <code>false</code> otherwise.
*/
function doBoundingBoxesIntersect(a, b) {
return a[0][0] <= b[1][0] && a[1][0] >= b[0][0] && a[0][1] <= b[1][1] &&
a[1][1] >= b[0][1];
}
/**
* Checks if a Point is on a line
* @param a line (interpreted as line, although given as line
* segment)
* @param b point
* @return <code>true</code> if point is on line, otherwise
* <code>false</code>
*/
function isPointOnLine(a, b) {
// Move the image, so that a[0] is on (0|0)
var aTmp = [[0,0], [a[1][0] - a[0][0],a[1][1] - a[0][1]]];
var bTmp = [b[0] - a[0][0], b[1] - a[0][1]];
var r = crossProduct(aTmp[1], bTmp);
return Math.abs(r) < EPSILON;
}
/**
* Checks if a point is right of a line. If the point is on the
* line, it is not right of the line.
* @param a line segment interpreted as a line
* @param b the point
* @return <code>true</code> if the point is right of the line,
* <code>false</code> otherwise
*/
function isPointRightOfLine(a, b) {
// Move the image, so that a[0] is on (0|0)
var aTmp = [[0,0], [a[1][0] - a[0][0], a[1][1] - a[0][1]]];
var bTmp = [b[0] - a[0][0], b[1] - a[0][1]];
return crossProduct(aTmp[1], bTmp) < 0;
}
/**
* Check if line segment first touches or crosses the line that is
* defined by line segment second.
*
* @param first line segment interpreted as line
* @param second line segment
* @return <code>true</code> if line segment first touches or
* crosses line second,
* <code>false</code> otherwise.
*/
function lineSegmentTouchesOrCrossesLine(a, b) {
return isPointOnLine(a, b[0]) || isPointOnLine(a, b[1]) ||
(isPointRightOfLine(a, b[0]) ^ isPointRightOfLine(a, b[1]));
}
function getBoundingBox(a) {
return [[Math.min(a[0][0], a[1][0]), Math.min(a[0][1], a[1][1])],
[Math.max(a[0][0], a[1][0]), Math.max(a[0][1], a[1][1])]];
}
/**
* Check if line segments intersect
* @param a first line segment
* @param b second line segment
* @return <code>true</code> if lines do intersect,
* <code>false</code> otherwise
*/
function doLinesIntersect(a, b) {
var box1 = getBoundingBox(a);
var box2 = getBoundingBox(b);
return doBoundingBoxesIntersect(box1, box2) &&
lineSegmentTouchesOrCrossesLine(a, b) &&
lineSegmentTouchesOrCrossesLine(b, a);
}
/** You know that lines a and b have an intersection and now you
want to get it!
*/
function getIntersection(a, b) {
//the intersection [(x1,y1), (x2, y2)]
//it might be a line or a single point. If it is a line,
//then x1 = x2 and y1 = y2
var x1, y1, x2, y2;
var tmp;
var m, t;
if (a[0][0] == a[1][0]) {
// Case (A)
// As a is a perfect vertical line, it cannot be represented
// nicely in a mathematical way. But we directly know that
//
x1 = a[0][0];
x2 = x1;
if (b[0][0] == b[1][0]) {
// Case (AA): all x are the same!
// Normalize
if(a[0][1] > a[1][1]) {
a = [a[1], a[0]];
}
if(b[0][1] > b[1][1]) {
b = [b[1], b[0]];
}
if(a[0][1] > b[0][1]) {
tmp = a;
a = b;
b = tmp;
}
// Now we know that the y-value of a[0] is the
// lowest of all 4 y values
// this means, we are either in case (AAA):
// a: x--------------x
// b: x---------------x
// or in case (AAB)
// a: x--------------x
// b: x-------x
// in both cases:
// get the relavant y intervall
y1 = b[0][1];
y2 = Math.min(a[1][1], b[1][1]);
} else {
// Case (AB)
// we can mathematically represent line b as
// y = m*x + t <=> t = y - m*x
// m = (y1-y2)/(x1-x2)
m = (b[0][1] - b[1][1]) / (b[0][0] - b[1][0]);
t = b[0][1] - m*b[0][0];
y1 = m*x1 + t;
y2 = y1;
}
} else if (b[0][0] == b[1][0]) {
// Case (B)
// essentially the same as Case (AB), but with
// a and b switched
x1 = b[0][0];
x2 = x1;
tmp = a;
a = b;
b = tmp;
m = (b[0][1] - b[1][1]) / (b[0][0] - b[1][0]);
t = b[0][1] - m*b[0][0];
y1 = m*x1 + t;
y2 = y1;
} else {
// Case (C)
// Both lines can be represented mathematically
var ma, mb, ta, tb;
ma = (a[0][1] - a[1][1]) / (a[0][0] - a[1][0]);
mb = (b[0][1] - b[1][1]) / (b[0][0] - b[1][0]);
ta = a[0][1] - ma*a[0][0];
tb = b[0][1] - mb*b[0][0];
if (ma == mb) {
// Case (CA)
// both lines are in parallel. As we know that they
// intersect, the intersection could be a line
// when we rotated this, it would be the same situation
// as in case (AA)
// Normalize
if(a[0][0] > a[1][0]) {
a = [a[1], a[0]];
}
if(b[0][0] > b[1][0]) {
b = [b[1], b[0]];
}
if(a[0][0] > b[0][0]) {
tmp = a;
a = b;
b = tmp;
}
// get the relavant x intervall
x1 = b[0][0];
x2 = Math.min(a[1][0], b[1][0]);
y1 = ma*x1+ta;
y2 = ma*x2+ta;
} else {
// Case (CB): only a point as intersection:
// y = ma*x+ta
// y = mb*x+tb
// ma*x + ta = mb*x + tb
// (ma-mb)*x = tb - ta
// x = (tb - ta)/(ma-mb)
x1 = (tb-ta)/(ma-mb);
y1 = ma*x1+ta;
x2 = x1;
y2 = y1;
}
}
return [[x1,y1], [x2,y2]];
}
// Code goes here
var projection = ol.proj.get('EPSG:3857');
var view = new ol.View({
center: [0, 0],
projection: projection,
extent: projection.getExtent(),
zoom: 2
});
var map = new ol.Map({
layers: [
new ol.layer.Tile({
source: new ol.source.OSM()
})
],
target: 'map',
controls: ol.control.defaults({
attributionOptions: /** @type {olx.control.AttributionOptions} */ ({
collapsible: false
}),
rotateOptions: {
autoHide: false
}
}),
view: view
});
var rotateMap = false;
$('#rotate').on('click', function() {
$(this).toggleClass('active');
rotateMap = !rotateMap;
});
var deviceOrientation = new ol.DeviceOrientation();
deviceOrientation.on(['change:heading'], function(event) {
var heading = event.target.getHeading() || 0;
var viewRotation = 0;
var markerRotation = 0;
if (rotateMap) {
viewRotation = -heading;
view.setCenter(geolocation.getPosition());
} else {
markerRotation = heading;
}
view.setRotation(viewRotation);
positionMarkerIcon.setRotation(markerRotation)
positionFeature.setStyle(new ol.style.Style({
image: positionMarkerIcon
}));
});
deviceOrientation.setTracking(true);
var geolocation = new ol.Geolocation({
projection: view.getProjection(),
tracking: true
});
geolocation.once('change:position', function() {
view.setCenter(geolocation.getPosition());
view.setZoom(12);
});
geolocation.on('change:position', function() {
var coords = [geolocation.getPosition(), target.getGeometry().getCoordinates()];
lineToTarget.setGeometry(new ol.geom.LineString(coords));
});
var positionFeature = new ol.Feature();
positionFeature.bindTo('geometry', geolocation, 'position')
.transform(function() {}, function(coordinates) {
return coordinates ? new ol.geom.Point(coordinates) : null;
});
var positionMarkerIcon = new ol.style.Icon({
src: "http://openlayers.org/en/master/examples/data/geolocation_marker_heading.png",
anchor: [11, 24],
anchorXUnits: 'pixels',
anchorYUnits: 'pixels'
});
var featuresOverlay = new ol.FeatureOverlay({
map: map,
features: [positionFeature]
});
// destination: the point the user is supposed to go to
var target = new ol.Feature({
geometry: new ol.geom.Point(
ol.proj.transform([Math.random() * 360 - 180, Math.random() * 180 - 90],
'EPSG:4326', 'EPSG:3857'))
});
var style = new ol.style.Style({
image: new ol.style.Circle({
radius: 5,
fill: new ol.style.Fill({color: 'red'}),
stroke: new ol.style.Stroke({color: 'black', width: 1})
})
});
target.setStyle(style);
featuresOverlay.addFeature(target);
// the line to target
var lineToTarget = new ol.Feature();
lineToTarget.setStyle(new ol.style.Style({
stroke: new ol.style.Stroke({
color: "white",
lineDash: [5, 5],
width: 2
})
}));
featuresOverlay.addFeature(lineToTarget);
var intersectionFeature = new ol.Feature();
featuresOverlay.addFeature(intersectionFeature);
intersectionIcon = new ol.style.Icon({
src: "arrow_skip_up.png",
anchor: [24, 0],
anchorXUnits: 'pixels',
anchorYUnits: 'pixels'
});
intersectionFeature.setStyle(new ol.style.Style({
image: intersectionIcon
}));
view.on('propertychange', function() {
if (!lineToTarget.getGeometry()) {
return;
}
var seg = lineToTarget.getGeometry().getCoordinates();
// the polygon representing the current visible area
// note: it's not really an extent
var size = map.getSize();
var width = size[0];
var height = size[1];
var box = [
map.getCoordinateFromPixel([0, 0]),
map.getCoordinateFromPixel([width, 0]),
map.getCoordinateFromPixel([width, height]),
map.getCoordinateFromPixel([0, height]),
map.getCoordinateFromPixel([0, 0])
];
var slope = Math.atan2(seg[1][1] - seg[0][1], seg[1][0] - seg[0][0]);
intersectionIcon.setRotation(Math.PI / 2 - slope + view.getRotation());
// calculate the intersection between line to destination and current visible
// extent
var len = box.length;
for (var i = 0; i < len - 1; i++) {
var a = [seg[0], seg[1]];
var b = [box[i], box[i + 1]];
if (doLinesIntersect(a, b)) {
intersection = getIntersection(a, b);
var point = new ol.geom.Point([intersection[0][0], intersection[0][1]]);
//featuresOverlay.addFeature(new ol.Feature(point));
intersectionFeature.setGeometry(point);
if (intersection) {
continue;
}
}
}
//var intersection = segmentExtentIntersection(segment, extent);
//intersectionFeature.setGeometry(new ol.geom.Point(intersection));
});
// convert degrees to radians
function degToRad(deg) {
return deg * Math.PI * 2 / 360;
}
// convert radians to degrees
function radToDeg(rad) {
return rad * 360 / (Math.PI * 2);
}
/**
* Calculates the intersection between segment and extent (bbox)
* @param seg the segment
* @param extent the extent
* @return the intersection coordinates or false
*/
//function segmentExtentIntersection(seg, extent) {
//var minx = extent[0];
//var miny = extent[1];
//var maxx = extent[2];
//var maxy = extent[3];
//var extentSegments = [
//[[minx, miny], [minx, maxy]],
//[[minx, maxy], [maxx, maxy]],
//[[maxx, maxy], [maxx, miny]],
//[[maxx, miny], [minx, miny]]
//];
//var intersection = false;
//for (var i=0; i < extentSegments.length; i++) {
//var a = [[seg[0][0], seg[0][1]], [seg[1][0], seg[1][1]]];
//var b = extentSegments[i];
//if (doLinesIntersect(a, b)) {
//intersection = getIntersection(a, b);
//return [intersection[0][0], intersection[0][1]];
//}
//}
//return intersection;
//}
/* Styles go here */
html, body, #map {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
#rotate {
position: absolute;
left: 10px;
bottom: 10px;
z-index: 2;
}
#rotate.active {
background-color: yellow;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment