Skip to content

Commit

Permalink
Accept custom project,unproject
Browse files Browse the repository at this point in the history
  • Loading branch information
Anand Thakker committed Jun 22, 2016
1 parent cd94dc9 commit ac92d09
Show file tree
Hide file tree
Showing 3 changed files with 216 additions and 52 deletions.
172 changes: 172 additions & 0 deletions debug/projection.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
<!DOCTYPE html>
<html>
<head>
<title>Mapbox GL JS debug page</title>
<meta charset='utf-8'>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">

<link rel='stylesheet' href='../dist/mapbox-gl.css' />
<style>
body { margin: 0; padding: 0; }
html, body, #map { height: 100%; }
#checkboxes {
position: absolute;
background: #fff;
top:0;
left:0;
padding:10px;
}
#buffer {
position: absolute;
top:100px;
left:0;
pointer-events: none;
}
#buffer div {
background-color: #fff;
padding: 5px 0;
text-indent: 10px;
white-space: nowrap;
text-shadow:
-1px -1px 0 #fff,
1px -1px 0 #fff,
-1px 1px 0 #fff,
1px 1px 0 #fff;
}
</style>
</head>

<body>
<div id='map'></div>
<div id='checkboxes'>
<input id='show-tile-boundaries-checkbox' name='show-tile-boundaries' type='checkbox'> <label for='show-tile-boundaries'>tile debug</label><br />
<input id='show-symbol-collision-boxes-checkbox' name='show-symbol-collision-boxes' type='checkbox'> <label for='show-symbol-collision-boxes'>collision debug</label><br />
<input id='show-overdraw-checkbox' name='show-overdraw' type='checkbox'> <label for='show-overdraw'>overdraw debug</label><br />
<input id='buffer-checkbox' name='buffer' type='checkbox'> <label for='buffer'>buffer stats</label>
</div>

<div id='buffer' style="display:none">
<em>Waiting for data...</em>
</div>

<script src='https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.3.14/proj4-src.js'></script>
<script src='mapbox-gl.js'></script>
<script src='access-token.js'></script>

<script>
var style = {
"version": 8,
"sources": {
"states": {
"type": "vector",
"url": "mapbox://devseed.states-albers"
}
},
"layers": [{
"id": "background",
"type": "background",
"paint": {
"background-color": "rgb(4,7,14)"
}
}, {
"id": "states",
"type": "line",
"source": "states",
"source-layer": "states",
"paint": {
"line-color": "#EC8D8D",
"line-width": {
"base": 1.5,
"stops": [
[
5,
0.75
],
[
18,
32
]
]
}
}
}]
};

// Custom projection. The dataset was generated by (a) projecting into albers
// (using precisely the projection string being passed to `proj4` below, and then
// (b) piping into tippecanoe with -s EPSG:3785.
// With this option set, tippecanoe does NOT try to project fron lnglat, but it
// DOES do an affine transformation to go from EPSG's projected space (meters) to
// tile space -- hence the small additional bits of math after `proj.forward` and
// before `proj.inverse`.

var proj = proj4('WGS84', '+proj=aea +lat_1=29.5 +lat_2=45.5 +lat_0=37.5 +lon_0=-96 +x_0=0 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs')
var C = 2 * Math.PI * 6378137.0;
var projection = {
latRange: undefined,
project: function (lnglat, worldSize) {
var pt = proj.forward([lnglat.lng, lnglat.lat])
var scale = (worldSize || this.worldSize);
pt[0] = scale * (pt[0] / C + 0.5);
pt[1] = scale * (0.5 - pt[1] / C);
return new mapboxgl.Point(pt[0], pt[1]);
},
unproject: function (point, worldSize) {
var scale = (worldSize || this.worldSize);
var ll = proj.inverse([
C * ((point.x / scale) - 0.5),
C * (0.5 - (point.y / scale))
]);
return mapboxgl.LngLat.convert(ll);
}
}

var map = window.map = new mapboxgl.Map({
container: 'map',
zoom: 5.5,
center: [-77.01866, 38.888],
style: style,
projection: projection,
hash: true
});

map.addControl(new mapboxgl.Navigation());
map.addControl(new mapboxgl.Geolocate());

map.on('click', function(e) {
if (e.originalEvent.shiftKey) return;
var ll = map.unproject(e.point);
(new mapboxgl.Popup())
.setLngLat(ll)
.setHTML(ll.lng.toFixed(3) + ', ' + ll.lat.toFixed(3))
.addTo(map);
});

document.getElementById('show-tile-boundaries-checkbox').onclick = function() {
map.showTileBoundaries = !!this.checked;
};

document.getElementById('show-symbol-collision-boxes-checkbox').onclick = function() {
map.showCollisionBoxes = !!this.checked;
};

document.getElementById('show-overdraw-checkbox').onclick = function() {
map.showOverdrawInspector = !!this.checked;
};

document.getElementById('buffer-checkbox').onclick = function() {
document.getElementById('buffer').style.display = this.checked ? 'block' : 'none';
};

// keyboard shortcut for comparing rendering with Mapbox GL native
document.onkeypress = function(e) {
if (e.charCode === 111 && !e.shiftKey && !e.metaKey && !e.altKey) {
var center = map.getCenter();
location.href = "mapboxgl://?center=" + center.lat + "," + center.lng + "&zoom=" + map.getZoom() + "&bearing=" + map.getBearing();
return false;
}
};
</script>

</body>
</html>
94 changes: 43 additions & 51 deletions js/geo/transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
var LngLat = require('./lng_lat'),
Point = require('point-geometry'),
Coordinate = require('./coordinate'),
util = require('../util/util'),
wrap = require('../util/util').wrap,
interp = require('../util/interpolate'),
TileCoord = require('../source/tile_coord'),
Expand All @@ -23,14 +24,22 @@ module.exports = Transform;
* @param {number} maxZoom
* @private
*/
function Transform(minZoom, maxZoom) {
function Transform(minZoom, maxZoom, projection) {
this.tileSize = 512; // constant

this._minZoom = minZoom || 0;
this._maxZoom = maxZoom || 22;

this.latRange = [-85.05113, 85.05113];

if (projection) {
util.extend(this, util.pick(projection, [
'project',
'unproject',
'latRange'
]));
}

this.width = 0;
this.height = 0;
this._center = new LngLat(0, 0);
Expand Down Expand Up @@ -142,52 +151,26 @@ Transform.prototype = {
scaleZoom: function(scale) { return Math.log(scale) / Math.LN2; },

project: function(lnglat, worldSize) {
return new Point(
this.lngX(lnglat.lng, worldSize || this.worldSize),
this.latY(lnglat.lat, worldSize || this.worldSize));
worldSize = worldSize || this.worldSize;
var x = (180 + lnglat.lng) * worldSize / 360;
var y = 180 / Math.PI * Math.log(Math.tan(Math.PI / 4 + lnglat.lat * Math.PI / 360));
y = (180 - y) * worldSize / 360;
return new Point(x, y);
},

unproject: function(point, worldSize) {
return new LngLat(
this.xLng(point.x, worldSize || this.worldSize),
this.yLat(point.y, worldSize || this.worldSize));
worldSize = worldSize || this.worldSize;
var lng = point.x * 360 / worldSize - 180;
var y2 = 180 - point.y * 360 / worldSize;
var lat = 360 / Math.PI * Math.atan(Math.exp(y2 * Math.PI / 180)) - 90;
return new LngLat(lng, lat);
},

get x() { return this.lngX(this.center.lng, this.worldSize); },
get y() { return this.latY(this.center.lat, this.worldSize); },

get point() { return new Point(this.x, this.y); },
get x () { return this.point.x; },
get y () { return this.point.y; },

/**
* latitude to absolute x coord
* @param {number} lon
* @param {number} worldSize
* @returns {number} pixel coordinate
* @private
*/
lngX: function(lng, worldSize) {
return (180 + lng) * worldSize / 360;
},

/**
* latitude to absolute y coord
* @param {number} lat
* @param {number} worldSize
* @returns {number} pixel coordinate
* @private
*/
latY: function(lat, worldSize) {
var y = 180 / Math.PI * Math.log(Math.tan(Math.PI / 4 + lat * Math.PI / 360));
return (180 - y) * worldSize / 360;
},

xLng: function(x, worldSize) {
return x * 360 / worldSize - 180;
},

yLat: function(y, worldSize) {
var y2 = 180 - y * 360 / worldSize;
return 360 / Math.PI * Math.atan(Math.exp(y2 * Math.PI / 180)) - 90;
get point() {
return this.project(this.center);
},

panBy: function(offset) {
Expand Down Expand Up @@ -234,11 +217,11 @@ Transform.prototype = {
*/
locationCoordinate: function(lnglat) {
var k = this.zoomScale(this.tileZoom) / this.worldSize,
ll = LngLat.convert(lnglat);
pt = this.project(LngLat.convert(lnglat));

return new Coordinate(
this.lngX(ll.lng, this.worldSize) * k,
this.latY(ll.lat, this.worldSize) * k,
pt.x * k,
pt.y * k,
this.tileZoom);
},

Expand All @@ -250,9 +233,7 @@ Transform.prototype = {
*/
coordinateLocation: function(coord) {
var worldSize = this.zoomScale(coord.zoom) || this.worldSize;
return new LngLat(
this.xLng(coord.column, worldSize),
this.yLat(coord.row, worldSize));
return this.unproject(new Point(coord.column, coord.row), worldSize);
},

pointCoordinate: function(p) {
Expand Down Expand Up @@ -329,22 +310,33 @@ Transform.prototype = {

_constrain: function() {
if (!this.center || !this.width || !this.height || this._constraining) return;
if (!this.latRange && !this.lngRange) return;

this._constraining = true;

var minY, maxY, minX, maxX, sy, sx, x2, y2,
size = this.size,
unmodified = this._unmodified;

var ul = this.project(LngLat.convert([
this.lngRange ? this.lngRange[0] : 0,
this.latRange ? this.latRange[1] : 0
]));

var lr = this.project(LngLat.convert([
this.lngRange ? this.lngRange[1] : 0,
this.latRange ? this.latRange[0] : 0
]));

if (this.latRange) {
minY = this.latY(this.latRange[1], this.worldSize);
maxY = this.latY(this.latRange[0], this.worldSize);
minY = ul.y;
maxY = lr.y;
sy = maxY - minY < size.y ? size.y / (maxY - minY) : 0;
}

if (this.lngRange) {
minX = this.lngX(this.lngRange[0], this.worldSize);
maxX = this.lngX(this.lngRange[1], this.worldSize);
minX = ul.x;
maxX = lr.x;
sx = maxX - minX < size.x ? size.x / (maxX - minX) : 0;
}

Expand Down
2 changes: 1 addition & 1 deletion js/ui/map.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ var Map = module.exports = function(options) {
}

this.animationLoop = new AnimationLoop();
this.transform = new Transform(options.minZoom, options.maxZoom);
this.transform = new Transform(options.minZoom, options.maxZoom, options.projection);

if (options.maxBounds) {
this.setMaxBounds(options.maxBounds);
Expand Down

0 comments on commit ac92d09

Please sign in to comment.