Skip to content

Commit 9d2d370

Browse files
committed
Merge pull request #516 from OpenGeoscience/map-rotation
Map rotation
2 parents 08c0606 + 57ed253 commit 9d2d370

File tree

27 files changed

+1562
-242
lines changed

27 files changed

+1562
-242
lines changed

examples/common/css/examples.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
html, body {
22
height: 100%;
33
width: 100%;
4+
-webkit-touch-callout: none;
5+
-webkit-user-select: none;
6+
-khtml-user-select: none;
7+
-moz-user-select: none;
8+
-ms-user-select: none;
9+
user-select: none;
410
}
511

612
#map {

examples/common/templates/index.jade

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ doctype html
22
html(lang="en")
33
head
44
meta(charset="UTF-8")
5+
link(rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon")
56
title= title
67

78
// Include common files

examples/tiles/index.jade

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,16 @@ block append mainContent
3535
.form-group(title="The starting zoom level. Changing this will also change the current zoom level.")
3636
label(for="map-zoom") Starting Zoom
3737
input#map-zoom.mapparam(param-name="zoom", placeholder="3")
38+
.form-group(title="Allow the map to be rotated.")
39+
label(for="map-allowRotation") Allow Rotation
40+
select#map-allowRotation.mapparam(param-name="allowRotation", placeholder="true")
41+
option(value="true") Yes
42+
option(value="false") No
43+
option(value="90") 90° increments
44+
option(value="30") 30° increments
45+
option(value="15") 15° increments
46+
option(value="5") 5° increments
47+
option(value="1") 1° increments
3848

3949
.form-group(title="The url used to fetch tiles. Use {x}, {y}, {z}, and {s} for templating.")
4050
label(for="layer-url") Tile URL

examples/tiles/main.css

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
1-
#map.debug-border .geo-tile-container img {
1+
#map.debug-border .geo-tile-container:before {
2+
content: "";
23
box-sizing: inherit;
34
border: 4px solid rgba(0,0,0,0.25);
5+
position: absolute;
6+
top: 0;
7+
left: 0;
8+
bottom: 0;
9+
right: 0;
10+
z-index: 1;
411
}
512
#map.debug-label .geo-tile-container:after {
613
content: attr(tile-reference);
714
position: absolute;
815
top: 5px;
916
left: 10px;
1017
font-size: 30px;
18+
line-height: 1.3em;
1119
color: black;
1220
}
1321
@keyframes imgfadein {

examples/tiles/main.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// This example should be tried with different query strings.
22

33
/* Many parameters can be adjusted via url query parameters:
4+
* allowRotation: 'true' to allow map rotation, 'false' to prevent it, or
5+
* allowable rotations in degrees.
46
* attribution: override the layer attribution text.
57
* clampBoundsX: 'true' to clamp movement in the horizontal direction.
68
* clampBoundsY: 'true' to clamp movement in the vertical direction.
@@ -19,6 +21,10 @@
1921
* min: minimum zoom level (default is 0).
2022
* max: maximum zoom level (default is 16 for maps, or the entire image for
2123
* images).
24+
* maxBoundsBottom: maximum bounds bottom value.
25+
* maxBoundsLeft: maximum bounds left value.
26+
* maxBoundsRight: maximum bounds right value.
27+
* maxBoundsTop: maximum bounds top value.
2228
* opacity: a css opacity value (typically a float from 0 to 1).
2329
* projection: 'parallel' or 'projection' for the camera projection.
2430
* renderer: 'vgl' (default), 'd3', 'null', or 'html'. This picks the
@@ -27,6 +33,7 @@
2733
* subdomains: a comma-separated string of subdomains to use in the {s} part
2834
* of the url parameter. If there are no commas in the string, each letter
2935
* is used by itself (e.g., 'abc' is the same as 'a,b,c').
36+
* unitsPerPixel: set the units per pixel at zoom level 0.
3037
* url: url to use for the map files. Placeholders are allowed. Default is
3138
* http://otile1.mqcdn.com/tiles/1.0.0/map/{z}/{x}/{y}.png . Other useful
3239
* urls are are: /data/tilefancy.png
@@ -92,6 +99,7 @@ $(function () {
9299
x: -98.0,
93100
y: 39.5
94101
},
102+
maxBounds: {},
95103
zoom: query.zoom !== undefined ? parseFloat(query.zoom) : 3
96104
};
97105
// Set the tile layer defaults to use the specified renderer and opacity
@@ -157,6 +165,21 @@ $(function () {
157165
if (query.min !== undefined) {
158166
mapParams.min = parseFloat(query.min);
159167
}
168+
if (query.maxBoundsLeft !== undefined) {
169+
mapParams.maxBounds.left = parseFloat(query.maxBoundsLeft);
170+
}
171+
if (query.maxBoundsRight !== undefined) {
172+
mapParams.maxBounds.right = parseFloat(query.maxBoundsRight);
173+
}
174+
if (query.maxBoundsTop !== undefined) {
175+
mapParams.maxBounds.top = parseFloat(query.maxBoundsTop);
176+
}
177+
if (query.maxBoundsBottom !== undefined) {
178+
mapParams.maxBounds.bottom = parseFloat(query.maxBoundsBottom);
179+
}
180+
if (query.allowRotation) {
181+
mapParams.allowRotation = get_allow_rotation(query.allowRotation);
182+
}
160183
if (query.attribution !== undefined) {
161184
layerParams.attribution = query.attribution;
162185
}
@@ -187,6 +210,9 @@ $(function () {
187210
// maximum zoom
188211
mapParams.unitsPerPixel = Math.pow(2, mapParams.max);
189212
}
213+
if (query.unitsPerPixel !== undefined) {
214+
mapParams.unitsPerPixel = parseFloat(query.unitsPerPixel);
215+
}
190216
// Populate boolean flags for the map
191217
$.each({
192218
clampBoundsX: 'clampBoundsX',
@@ -249,6 +275,10 @@ $(function () {
249275
var processedValue = (ctl.is('[type="checkbox"]') ?
250276
(value === 'true') : value);
251277
switch (param) {
278+
case 'allowRotation':
279+
mapParams.allowRotation = get_allow_rotation(value);
280+
map.allowRotation(mapParams.allowRotation);
281+
break;
252282
case 'debug':
253283
$('#map').toggleClass('debug-label', (
254284
value === 'true' || value === 'all'))
@@ -326,4 +356,18 @@ $(function () {
326356
window.location.pathname + '?' + $.param(query);
327357
window.history.replaceState(query, '', newurl);
328358
}
359+
360+
/* Return the value to set for the allowRotation parameter.
361+
* @param value the value in the query string.
362+
* @returns true, false, or a function.
363+
*/
364+
function get_allow_rotation(value) {
365+
if (!parseFloat(value)) {
366+
return value !== 'false';
367+
}
368+
return function (rotation) {
369+
var factor = 180 / Math.PI / parseFloat(value);
370+
return Math.round(rotation * factor) / factor;
371+
};
372+
}
329373
});

examples/transitions/index.jade

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ block append mainContent
66
button#elastic-to-moscow Elastic to Moscow
77
button#bounce-to-istanbul Bounce to Istanbul
88
button#fly-to-bern Fly to Bern
9-
9+
button#spin-to-budapest Spin to Budapest

examples/transitions/main.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ $(function () {
1010
});
1111

1212
// Add an OSM layer
13-
var osm = map.createLayer('osm',{
13+
var osm = map.createLayer('osm', {
1414
baseUrl: 'http://otile1.mqcdn.com/tiles/1.0.0/map'
1515
});
1616

@@ -63,4 +63,12 @@ $(function () {
6363
interp: d3.interpolateZoom
6464
});
6565
});
66+
67+
$('#spin-to-budapest').click(function () {
68+
map.transition({
69+
center: {x: 19.0514, y: 47.4925},
70+
rotation: Math.PI * 2,
71+
duration: 2000
72+
});
73+
});
6674
});

src/core/camera.js

Lines changed: 92 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
* camera provides a methods for converting between a map's coordinate system
88
* to display pixel coordinates.
99
*
10-
* For the moment, all camera trasforms are assumed to be expressible as
10+
* For the moment, all camera transforms are assumed to be expressible as
1111
* 4x4 matrices. More general cameras may follow that break this assumption.
1212
*
1313
* The interface for the camera is relatively stable for "map-like" views,
14-
* i.e. when the camera is pointing in the direction [0, 0, -1], and placed
14+
* e.g. when the camera is pointing in the direction [0, 0, -1], and placed
1515
* above the z=0 plane. More general view changes and events have not yet
1616
* been defined.
1717
*
@@ -27,11 +27,13 @@
2727
* * {@link geo.event.camera.viewport} when the viewport changes
2828
*
2929
* By convention, protected methods do not update the internal matrix state,
30-
* public methods do. For now, there are two primary methods that are
31-
* inteded to be used by external classes to mutate the internal state:
30+
* public methods do. There are a few primary methods that are intended to
31+
* be used by external classes to mutate the internal state:
3232
*
3333
* * bounds: Set the visible bounds (for initialization and zooming)
3434
* * pan: Translate the camera in x/y by an offset (for panning)
35+
* * viewFromCenterSizeRotation: set the camera view based on a center
36+
* point, boundary size, and rotation angle.
3537
*
3638
* @class
3739
* @extends geo.object
@@ -521,22 +523,21 @@
521523
* @returns {object} bounds object
522524
*/
523525
this._getBounds = function () {
524-
var pt, bds = {};
525-
526-
527-
// get lower bounds
528-
pt = this.displayToWorld({
529-
x: 0, y: this._viewport.height
526+
var ul, ur, ll, lr, bds = {};
527+
528+
// get corners
529+
ul = this.displayToWorld({x: 0, y: 0});
530+
ur = this.displayToWorld({x: this._viewport.width, y: 0});
531+
ll = this.displayToWorld({x: 0, y: this._viewport.height});
532+
lr = this.displayToWorld({
533+
x: this._viewport.width,
534+
y: this._viewport.height
530535
});
531-
bds.left = pt.x;
532-
bds.bottom = pt.y;
533536

534-
// get upper bounds
535-
pt = this.displayToWorld({
536-
x: this._viewport.width, y: 0
537-
});
538-
bds.right = pt.x;
539-
bds.top = pt.y;
537+
bds.left = Math.min(ul.x, ur.x, ll.x, lr.x);
538+
bds.bottom = Math.min(ul.y, ur.y, ll.y, lr.y);
539+
bds.right = Math.max(ul.x, ur.x, ll.x, lr.x);
540+
bds.top = Math.max(ul.y, ur.y, ll.y, lr.y);
540541

541542
return bds;
542543
};
@@ -558,19 +559,45 @@
558559
* @return {this} Chainable
559560
*/
560561
this._setBounds = function (bounds) {
562+
var size = {
563+
width: bounds.right - bounds.left,
564+
height: bounds.top - bounds.bottom
565+
};
566+
var center = {
567+
x: (bounds.left + bounds.right) / 2,
568+
y: (bounds.bottom + bounds.top) / 2
569+
};
570+
571+
this._viewFromCenterSizeRotation(center, size, 0);
572+
return this;
573+
};
561574

575+
/**
576+
* Sets the view matrix so that the given world center is centered, at
577+
* least a certain width and height are visible, and a rotation is applied.
578+
* The resulting bounds may be larger in width or height than the values if
579+
* the viewport is a different aspect ratio.
580+
*
581+
* @protected
582+
* @param {object} center
583+
* @param {number} center.x
584+
* @param {number} center.y
585+
* @param {object} size
586+
* @param {number} size.width
587+
* @param {number} size.height
588+
* @param {number} rotation in clockwise radians. Optional
589+
* @return {this} Chainable
590+
*/
591+
this._viewFromCenterSizeRotation = function (center, size, rotation) {
562592
var translate = geo.util.vec3AsArray(),
563593
scale = geo.util.vec3AsArray(),
564594
c_ar, v_ar, w, h;
565595

566-
bounds.near = bounds.near || 0;
567-
bounds.far = bounds.far || 1;
568-
569596
// reset view to the identity
570597
this._resetView();
571598

572-
w = Math.abs(bounds.right - bounds.left);
573-
h = Math.abs(bounds.top - bounds.bottom);
599+
w = Math.abs(size.width);
600+
h = Math.abs(size.height);
574601
c_ar = w / h;
575602
v_ar = this._viewport.width / this._viewport.height;
576603

@@ -589,12 +616,26 @@
589616
scale[2] = 1;
590617
this._scale(scale);
591618

619+
if (rotation) {
620+
this._rotate(rotation);
621+
}
622+
592623
// translate to the new center.
593-
translate[0] = -(bounds.left + bounds.right) / 2;
594-
translate[1] = -(bounds.bottom + bounds.top) / 2;
624+
translate[0] = -center.x;
625+
translate[1] = -center.y;
595626
translate[2] = 0;
596627

597628
this._translate(translate);
629+
630+
return this;
631+
};
632+
633+
/**
634+
* Public exposure of the viewFromCenterSizeRotation function.
635+
*/
636+
this.viewFromCenterSizeRotation = function (center, size, rotation) {
637+
this._viewFromCenterSizeRotation(center, size, rotation);
638+
this._update();
598639
return this;
599640
};
600641

@@ -624,6 +665,9 @@
624665
* @param {number} zoom The zoom scale to apply
625666
*/
626667
this.zoom = function (zoom) {
668+
if (zoom === 1) {
669+
return;
670+
}
627671
mat4.scale(this._view, this._view, [
628672
zoom,
629673
zoom,
@@ -632,6 +676,29 @@
632676
this._update();
633677
};
634678

679+
/**
680+
* Rotate the view matrix by the given amount.
681+
*
682+
* @param {number} rotation Counter-clockwise rotation angle in radians.
683+
* @param {object} center Center of rotation in world space coordinates.
684+
* @param {vec3} axis acis of rotation. Defaults to [0, 0, -1]
685+
*/
686+
this._rotate = function (rotation, center, axis) {
687+
if (!rotation) {
688+
return;
689+
}
690+
axis = axis || [0, 0, -1];
691+
if (!center) {
692+
center = [0, 0, 0];
693+
} else if (center.x !== undefined) {
694+
center = [center.x || 0, center.y || 0, center.z || 0];
695+
}
696+
var invcenter = [-center[0], -center[1], -center[2]];
697+
mat4.translate(this._view, this._view, center);
698+
mat4.rotate(this._view, this._view, rotation, axis);
699+
mat4.translate(this._view, this._view, invcenter);
700+
};
701+
635702
/**
636703
* Returns a CSS transform that converts (by default) from world coordinates
637704
* into display coordinates. This allows users of this module to

src/core/event.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ geo.event = {};
3232
//
3333
// geo.event.update = 'geo_update';
3434
// geo.event.opacityUpdate = 'geo_opacityUpdate';
35-
// geo.event.layerToggle = 'geo_layerToggle';
3635
// geo.event.layerSelect = 'geo_layerSelect';
3736
// geo.event.layerUnselect = 'geo_layerUnselect';
3837
// geo.event.query = 'geo_query';
@@ -71,8 +70,8 @@ geo.event.zoom = 'geo_zoom';
7170

7271
//////////////////////////////////////////////////////////////////////////////
7372
/**
74-
* Triggered when the map is rotated around the map center (pointing downward
75-
* so that positive angles are clockwise rotations).
73+
* Triggered when the map is rotated around the current map center (pointing
74+
* downward so that positive angles are clockwise rotations).
7675
*
7776
* @property {number} angle The angle of the rotation in radians
7877
*/

0 commit comments

Comments
 (0)