├── .gitignore ├── examples ├── node │ ├── .gitignore │ ├── README │ ├── package.json │ └── modestmaps-static.js ├── zoompan │ └── zoompan.pdf ├── geojson │ ├── icons │ │ ├── ALL.png │ │ ├── ARSON.png │ │ ├── MURDER.png │ │ ├── THEFT.png │ │ ├── ALCOHOL.png │ │ ├── BURGLARY.png │ │ ├── NARCOTICS.png │ │ ├── ROBBERY.png │ │ ├── VANDALISM.png │ │ ├── PROSTITUTION.png │ │ ├── SIMPLE_ASSAULT.png │ │ ├── VEHICLE_THEFT.png │ │ ├── AGGRAVATED_ASSAULT.png │ │ └── DISTURBING_THE_PEACE.png │ └── index.html ├── bubble │ ├── README │ ├── index.html │ └── follower-canvas.js ├── extent-selector │ └── 1px.png ├── hurricanes │ ├── README │ ├── mapcontrols-raphael.js │ ├── hurricanes-raph.html │ ├── polygonmarker-raphael.js │ └── polygonmarker-canvas.js ├── flickr │ ├── bluemarble.js │ ├── index.html │ └── follower.js ├── README ├── chaining │ └── index.html ├── simple │ ├── minimal-maps.html │ ├── index.html │ └── follower.js ├── cloudmade │ ├── cloudmade.js │ ├── full.html │ └── index.html ├── microsoft │ ├── index.html │ └── bing.js ├── zoombox │ ├── index.html │ └── zoombox.js ├── tilecache │ ├── index.html │ └── tilecache.js ├── static │ └── index.html ├── placeholder │ └── index.html ├── anyscale │ └── index.html ├── hash │ └── index.html ├── templated │ ├── index.html │ └── follower.js ├── spotlight │ ├── index.html │ └── spotlight.js ├── show-hide │ └── index.html ├── touch │ └── index.html ├── greatcircle │ └── index.html ├── navwindow │ └── index.html ├── enforce-limits │ └── index.html ├── layers │ └── index.html ├── sprite-tiles │ ├── index.html │ └── modestmaps.sprite-tiles.js ├── cabs │ ├── index.html │ └── cab-follower.js ├── twomaps │ └── index.html ├── hello-oakland │ └── index.html ├── markerclip │ └── index.html └── polar │ └── polar.js ├── tools ├── yuicompressor-2.4.2.jar └── README ├── test └── browser │ ├── lib │ ├── jasmine-1.1.0.rc1 │ │ ├── jasmine_favicon.png │ │ ├── MIT.LICENSE │ │ └── jasmine.css │ ├── jasmine-1.2.0.rc3 │ │ ├── MIT.LICENSE │ │ └── jasmine.css │ └── happen.js │ ├── spec │ ├── Layer.js │ ├── Util.js │ ├── Location.js │ ├── Provider.js │ ├── Coordinate.js │ ├── Point.js │ ├── MouseWheelHandler.js │ ├── Hash.js │ ├── Extent.js │ ├── Projection.js │ ├── Transformation.js │ ├── DoubleClickHandler.js │ ├── DragHandler.js │ └── Map.js │ └── index.html ├── src ├── end.js ├── start.js ├── point.js ├── convenience.js ├── callbacks.js ├── transformation.js ├── projection.js ├── coordinate.js ├── location.js ├── hash.js ├── extent.js ├── provider.js ├── mouse.js └── utils.js ├── package.json ├── Makefile └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /examples/node/.gitignore: -------------------------------------------------------------------------------- 1 | map.png 2 | -------------------------------------------------------------------------------- /examples/zoompan/zoompan.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stamen/modestmaps-js/HEAD/examples/zoompan/zoompan.pdf -------------------------------------------------------------------------------- /examples/geojson/icons/ALL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stamen/modestmaps-js/HEAD/examples/geojson/icons/ALL.png -------------------------------------------------------------------------------- /tools/yuicompressor-2.4.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stamen/modestmaps-js/HEAD/tools/yuicompressor-2.4.2.jar -------------------------------------------------------------------------------- /examples/bubble/README: -------------------------------------------------------------------------------- 1 | excanvas is from http://code.google.com/p/explorercanvas/ and licensed under Apache License 2.0 2 | -------------------------------------------------------------------------------- /examples/extent-selector/1px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stamen/modestmaps-js/HEAD/examples/extent-selector/1px.png -------------------------------------------------------------------------------- /examples/geojson/icons/ARSON.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stamen/modestmaps-js/HEAD/examples/geojson/icons/ARSON.png -------------------------------------------------------------------------------- /examples/geojson/icons/MURDER.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stamen/modestmaps-js/HEAD/examples/geojson/icons/MURDER.png -------------------------------------------------------------------------------- /examples/geojson/icons/THEFT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stamen/modestmaps-js/HEAD/examples/geojson/icons/THEFT.png -------------------------------------------------------------------------------- /examples/geojson/icons/ALCOHOL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stamen/modestmaps-js/HEAD/examples/geojson/icons/ALCOHOL.png -------------------------------------------------------------------------------- /examples/geojson/icons/BURGLARY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stamen/modestmaps-js/HEAD/examples/geojson/icons/BURGLARY.png -------------------------------------------------------------------------------- /examples/geojson/icons/NARCOTICS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stamen/modestmaps-js/HEAD/examples/geojson/icons/NARCOTICS.png -------------------------------------------------------------------------------- /examples/geojson/icons/ROBBERY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stamen/modestmaps-js/HEAD/examples/geojson/icons/ROBBERY.png -------------------------------------------------------------------------------- /examples/geojson/icons/VANDALISM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stamen/modestmaps-js/HEAD/examples/geojson/icons/VANDALISM.png -------------------------------------------------------------------------------- /examples/geojson/icons/PROSTITUTION.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stamen/modestmaps-js/HEAD/examples/geojson/icons/PROSTITUTION.png -------------------------------------------------------------------------------- /examples/geojson/icons/SIMPLE_ASSAULT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stamen/modestmaps-js/HEAD/examples/geojson/icons/SIMPLE_ASSAULT.png -------------------------------------------------------------------------------- /examples/geojson/icons/VEHICLE_THEFT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stamen/modestmaps-js/HEAD/examples/geojson/icons/VEHICLE_THEFT.png -------------------------------------------------------------------------------- /examples/geojson/icons/AGGRAVATED_ASSAULT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stamen/modestmaps-js/HEAD/examples/geojson/icons/AGGRAVATED_ASSAULT.png -------------------------------------------------------------------------------- /examples/geojson/icons/DISTURBING_THE_PEACE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stamen/modestmaps-js/HEAD/examples/geojson/icons/DISTURBING_THE_PEACE.png -------------------------------------------------------------------------------- /test/browser/lib/jasmine-1.1.0.rc1/jasmine_favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stamen/modestmaps-js/HEAD/test/browser/lib/jasmine-1.1.0.rc1/jasmine_favicon.png -------------------------------------------------------------------------------- /examples/hurricanes/README: -------------------------------------------------------------------------------- 1 | The Katrina data file here is provided for example purposes only. 2 | 3 | The data comes from Hurricane Mapping - www.hurricanemapping.com 4 | -------------------------------------------------------------------------------- /examples/node/README: -------------------------------------------------------------------------------- 1 | This example works with node.js and (ideally) npm. 2 | 3 | The included package.json file means you should be able to type `npm install` in this folder 4 | to pull down all dependencies. 5 | 6 | Then do `node modestmaps-static.js` to run the example server. 7 | 8 | Example URL, San Francisco at zoom level 11 on OpenStreetMap, in an 800x600 image: 9 | http://localhost:3000/map?provider=osm&width=800&height=600&zoom=11&lat=37.774929&lon=-122.419415 10 | 11 | -------------------------------------------------------------------------------- /test/browser/spec/Layer.js: -------------------------------------------------------------------------------- 1 | describe('Layer', function() { 2 | // Currently not testing subdomain-based templatedmapprovider, since 3 | // the implementation should be kind of undefined. 4 | it('layer can be created and destroyed', function() { 5 | var p = new MM.TemplatedLayer( 6 | 'http://{S}.tile.openstreetmap.org/{Z}/{X}/{Y}.png', ['a']); 7 | var l = new MM.Layer(p); 8 | 9 | l.destroy(); 10 | expect(l.map).toEqual(null); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /test/browser/spec/Util.js: -------------------------------------------------------------------------------- 1 | function Receiver() { } 2 | Receiver.prototype.receive = function() { }; 3 | 4 | describe('Util', function() { 5 | it('can get a frame', function() { 6 | sink = new Receiver(); 7 | spyOn(sink, 'receive'); 8 | 9 | runs(function() { 10 | MM.getFrame(sink.receive); 11 | }); 12 | 13 | waits(200); 14 | 15 | runs(function() { 16 | expect(sink.receive).toHaveBeenCalled(); 17 | }); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /examples/flickr/bluemarble.js: -------------------------------------------------------------------------------- 1 | MM.BlueMarbleProvider = function() { 2 | MM.MapProvider.call(this, function(coordinate) { 3 | coordinate = this.sourceCoordinate(coordinate); 4 | if (!coordinate) return null; 5 | var img = coordinate.zoom.toFixed(0) +'-r'+ coordinate.row.toFixed(0) +'-c'+ coordinate.column.toFixed(0) + '.jpg'; 6 | return 'http://s3.amazonaws.com/com.modestmaps.bluemarble/' + img; 7 | }); 8 | }; 9 | 10 | com.modestmaps.extend(MM.BlueMarbleProvider, MM.MapProvider); 11 | -------------------------------------------------------------------------------- /test/browser/spec/Location.js: -------------------------------------------------------------------------------- 1 | describe('Location', function() { 2 | it('creates a location', function() { 3 | var p = new MM.Location(0, 1); 4 | expect(p.lon).toEqual(1); 5 | expect(p.lat).toEqual(0); 6 | }); 7 | it('can be copied', function() { 8 | var p = new MM.Location(0, 1); 9 | expect(p.lon).toEqual(1); 10 | expect(p.lat).toEqual(0); 11 | var cp = p.copy(); 12 | expect(cp.lon).toEqual(1); 13 | expect(cp.lat).toEqual(0); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /test/browser/spec/Provider.js: -------------------------------------------------------------------------------- 1 | describe('Providers', function() { 2 | // Currently not testing subdomain-based templatedmapprovider, since 3 | // the implementation should be kind of undefined. 4 | it('basic templatedmapprovider', function() { 5 | var p = new MM.TemplatedMapProvider( 6 | 'http://{S}.tile.openstreetmap.org/{Z}/{X}/{Y}.png', ['a']); 7 | 8 | expect(p.getTile(new MM.Coordinate(1225, 1832, 12))).toEqual( 9 | 'http://a.tile.openstreetmap.org/12/1832/1225.png'); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /test/browser/spec/Coordinate.js: -------------------------------------------------------------------------------- 1 | describe('Coordinate', function() { 2 | var coordinate; 3 | 4 | beforeEach(function() { 5 | coordinate = new MM.Coordinate(0, 0, 2); 6 | }); 7 | 8 | it('generates a key', function() { 9 | expect(typeof coordinate.toKey()).toEqual('string'); 10 | }); 11 | 12 | it('can provide a zoomed-in coordinate', function() { 13 | expect((coordinate.zoomBy(1)).zoom).toEqual(3); 14 | }); 15 | 16 | it('can provide a zoomed-out coordinate', function() { 17 | expect((coordinate.zoomBy(-1)).zoom).toEqual(1); 18 | }); 19 | 20 | }); 21 | -------------------------------------------------------------------------------- /src/end.js: -------------------------------------------------------------------------------- 1 | if (typeof module !== 'undefined' && module.exports) { 2 | module.exports = { 3 | Point: MM.Point, 4 | Projection: MM.Projection, 5 | MercatorProjection: MM.MercatorProjection, 6 | LinearProjection: MM.LinearProjection, 7 | Transformation: MM.Transformation, 8 | Location: MM.Location, 9 | MapProvider: MM.MapProvider, 10 | TemplatedMapProvider: MM.TemplatedMapProvider, 11 | Coordinate: MM.Coordinate, 12 | deriveTransformation: MM.deriveTransformation 13 | }; 14 | } 15 | })(MM); 16 | -------------------------------------------------------------------------------- /examples/node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Tom Carden (http://www.tom-carden.co.uk)", 3 | "name": "modestmaps-static", 4 | "description": "create a static image composed of several map tiles", 5 | "version": "0.0.0", 6 | "homepage": "https://github.com/stamen/modestmaps-js/", 7 | "repository": { 8 | "type": "git", 9 | "url": "git://github.com/stamen/modestmaps-js.git" 10 | }, 11 | "main": "modestmaps-static.js", 12 | "engines": { 13 | "node": "~v0.4.2" 14 | }, 15 | "dependencies": { 16 | "express": "~2.4.3", 17 | "canvas": "~0.7.0", 18 | "get": "~0.4.0" 19 | }, 20 | "devDependencies": {} 21 | } 22 | -------------------------------------------------------------------------------- /examples/README: -------------------------------------------------------------------------------- 1 | Some of Modest Maps JS is experimental, and under active development. These examples reflect this exploration, and occasionally use external libraries (like jquery, raphael, excanvas and dojo) that the core Modest Maps code does not depend on. 2 | 3 | Plans involve experimentation with the following: 4 | 5 | * SVG and Canvas drawing 6 | * Webkit/Gecko CSS transforms/animations 7 | * IE CSS filters. 8 | * arbitrary zoom levels 9 | * rotations 10 | * touch events 11 | * ipad/iphone/ipod/android support 12 | 13 | Contributions are welcome! Please contact us via github (file an issue or message @RandomEtc, @migurski or @tmcw) if you would like to get involved. 14 | 15 | -------------------------------------------------------------------------------- /src/start.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Modest Maps JS v1.0.0-beta1 3 | * http://modestmaps.com/ 4 | * 5 | * Copyright (c) 2011 Stamen Design, All Rights Reserved. 6 | * 7 | * Open source under the BSD License. 8 | * http://creativecommons.org/licenses/BSD/ 9 | * 10 | * Versioned using Semantic Versioning (v.major.minor.patch) 11 | * See CHANGELOG and http://semver.org/ for more details. 12 | * 13 | */ 14 | 15 | var previousMM = MM; 16 | 17 | // namespacing for backwards-compatibility 18 | if (!com) { 19 | var com = {}; 20 | if (!com.modestmaps) com.modestmaps = {}; 21 | } 22 | 23 | var MM = com.modestmaps = { 24 | noConflict: function() { 25 | MM = previousMM; 26 | return this; 27 | } 28 | }; 29 | 30 | (function(MM) { 31 | -------------------------------------------------------------------------------- /examples/chaining/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Modest Maps JS - Chaining Wrapper 5 | 6 | 16 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /examples/simple/minimal-maps.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Modest Maps JS 5 | 6 | 20 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /test/browser/spec/Point.js: -------------------------------------------------------------------------------- 1 | describe('Point', function() { 2 | it('creates a point', function() { 3 | var p = new MM.Point(0, 1); 4 | expect(p.x).toEqual(0); 5 | expect(p.y).toEqual(1); 6 | }); 7 | 8 | it('correctly computes distance to another point', function() { 9 | var p = new MM.Point(0, 0); 10 | var q = new MM.Point(0, 10); 11 | expect(MM.Point.distance(p, q)).toEqual(10); 12 | 13 | var p1 = new MM.Point(0, 0); 14 | var q1 = new MM.Point(5, 2); 15 | expect(MM.Point.distance(p1, q1)).toBeCloseTo(5.3851); 16 | }); 17 | 18 | it('correctly interpolates positions', function() { 19 | var p = new MM.Point(0, 0); 20 | var q = new MM.Point(0, 10); 21 | expect(MM.Point.interpolate(p, q, 0.5).y).toEqual(5); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "modestmaps", 3 | "description": "a display and interaction library for tile-based maps", 4 | "version": "1.0.0-beta1", 5 | "author": { 6 | "name": "Tom Carden", 7 | "email": "tom@tom-carden.co.uk", 8 | "url": "http://www.tom-carden.co.uk/" 9 | }, 10 | "contributors": [ 11 | "Mike Migurski ", 12 | "Shawn Allen ", 13 | "Tom MacWright " 14 | ], 15 | "keywords": ["map", "geo", "browser"], 16 | "main": "./modestmaps.js", 17 | "homepage": "https://github.com/stamen/modestmaps-js", 18 | "repository": { 19 | "type" : "git", 20 | "url" : "git://github.com/stamen/modestmaps-js.git" 21 | }, 22 | "dependencies": {}, 23 | "devDependencies": { 24 | "docco": "~0.3.0", 25 | "uglify-js": "~1.0.0" 26 | }, 27 | "directories": { } 28 | } 29 | -------------------------------------------------------------------------------- /examples/cloudmade/cloudmade.js: -------------------------------------------------------------------------------- 1 | // namespacing! 2 | if (!MM) { 3 | var MM = { }; 4 | } 5 | 6 | MM.CloudMadeProvider = function(key, style) { 7 | this.key = key; 8 | this.style = style; 9 | this.tileWidth = 256; 10 | this.tileHeight = 256; 11 | }; 12 | 13 | MM.CloudMadeProvider.prototype = { 14 | key: null, 15 | style: null, 16 | getTile: function(coord) { 17 | coord = this.sourceCoordinate(coord); 18 | var worldSize = Math.pow(2, coord.zoom); 19 | var server = new Array('a.', 'b.', 'c.', '')[parseInt(worldSize * coord.row + coord.column, 10) % 4]; 20 | var imgPath = new Array(this.key, this.style, this.tileWidth, coord.zoom, coord.column, coord.row).join('/'); 21 | return 'http://' + server + 'tile.cloudmade.com/' + imgPath + '.png'; 22 | } 23 | }; 24 | 25 | MM.extend(MM.CloudMadeProvider, MM.MapProvider); 26 | -------------------------------------------------------------------------------- /src/point.js: -------------------------------------------------------------------------------- 1 | // Point 2 | MM.Point = function(x, y) { 3 | this.x = parseFloat(x); 4 | this.y = parseFloat(y); 5 | }; 6 | 7 | MM.Point.prototype = { 8 | x: 0, 9 | y: 0, 10 | toString: function() { 11 | return "(" + this.x.toFixed(3) + ", " + this.y.toFixed(3) + ")"; 12 | }, 13 | copy: function() { 14 | return new MM.Point(this.x, this.y); 15 | } 16 | }; 17 | 18 | // Get the euclidean distance between two points 19 | MM.Point.distance = function(p1, p2) { 20 | return Math.sqrt( 21 | Math.pow(p2.x - p1.x, 2) + 22 | Math.pow(p2.y - p1.y, 2)); 23 | }; 24 | 25 | // Get a point between two other points, biased by `t`. 26 | MM.Point.interpolate = function(p1, p2, t) { 27 | return new MM.Point( 28 | p1.x + (p2.x - p1.x) * t, 29 | p1.y + (p2.y - p1.y) * t); 30 | }; 31 | -------------------------------------------------------------------------------- /examples/cloudmade/full.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Modest Maps JS 5 | 6 | 7 | 21 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | JS_FILES = \ 2 | src/start.js \ 3 | src/utils.js \ 4 | src/point.js \ 5 | src/coordinate.js \ 6 | src/location.js \ 7 | src/extent.js \ 8 | src/transformation.js \ 9 | src/projection.js \ 10 | src/provider.js \ 11 | src/mouse.js \ 12 | src/hash.js \ 13 | src/touch.js \ 14 | src/callbacks.js \ 15 | src/requests.js \ 16 | src/layer.js \ 17 | src/map.js \ 18 | src/convenience.js \ 19 | src/end.js 20 | 21 | all: modestmaps.js modestmaps.min.js 22 | 23 | modestmaps.min.js: modestmaps.js 24 | rm -f modestmaps.min.js 25 | java -jar tools/yuicompressor-2.4.2.jar modestmaps.js > modestmaps.min.js 26 | chmod a-w modestmaps.min.js 27 | 28 | modestmaps.js: $(JS_FILES) Makefile 29 | rm -f modestmaps.js 30 | cat $(JS_FILES) >> modestmaps.js 31 | chmod a-w modestmaps.js 32 | 33 | clean: 34 | rm modestmaps.js 35 | rm modestmaps.min.js 36 | 37 | doc: 38 | ./node_modules/.bin/docco src/*.js 39 | 40 | tests: 41 | ./node_modules/.bin/expresso test/*.test.js 42 | -------------------------------------------------------------------------------- /examples/microsoft/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Modest Maps JS 5 | 6 | 7 | 19 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /examples/zoombox/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Modest Maps JS - Zoom Box 5 | 6 | 7 | 26 | 35 | 36 | 37 |
38 |
39 | 40 | 41 | -------------------------------------------------------------------------------- /examples/tilecache/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Modest Maps JS 5 | 6 | 7 | 15 | 16 | 17 |

Modest Maps JS

18 |

zoom in | zoom out 19 |
pan left | pan right | pan down | pan up

20 |
21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /test/browser/lib/jasmine-1.1.0.rc1/MIT.LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008-2011 Pivotal Labs 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /test/browser/lib/jasmine-1.2.0.rc3/MIT.LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008-2011 Pivotal Labs 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /test/browser/spec/MouseWheelHandler.js: -------------------------------------------------------------------------------- 1 | describe('MouseWheelHandler', function() { 2 | var map; 3 | 4 | beforeEach(function() { 5 | div = document.createElement('div'); 6 | div.id = +new Date(); 7 | div.style.width = 500; 8 | div.style.height = 500; 9 | 10 | var template = 'http://{S}tile.openstreetmap.org/{Z}/{X}/{Y}.png'; 11 | var subdomains = [ '', 'a.', 'b.', 'c.' ]; 12 | var provider = new MM.TemplatedLayer(template, subdomains); 13 | 14 | map = new MM.Map(div, provider, [ 15 | new MM.MouseWheelHandler() 16 | ]); 17 | map.setCenterZoom(new MM.Location(0, 0), 0); 18 | }); 19 | 20 | it('zooms in the map', function() { 21 | runs(function() { 22 | happen.once(map.parent, { 23 | type: 'mousewheel', 24 | detail: -100 25 | }); 26 | }); 27 | 28 | waits(300); 29 | 30 | runs(function() { 31 | happen.once(map.parent, { 32 | type: 'mousewheel', 33 | detail: -200 34 | }); 35 | expect(map.getZoom()).toEqual(1); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/browser/spec/Hash.js: -------------------------------------------------------------------------------- 1 | describe('Hash', function() { 2 | var map, initial_zoom = 10; 3 | 4 | beforeEach(function() { 5 | var div = document.createElement('div'); 6 | 7 | window.location.hash = ''; 8 | 9 | map = new MM.Map(div, new MM.TemplatedLayer( 10 | 'http://{S}tile.openstreetmap.org/{Z}/{X}/{Y}.png', ['a.']), 11 | new MM.Point(10, 10)); 12 | 13 | new MM.Hash(map); 14 | }); 15 | 16 | it('should not mess with map movement', function() { 17 | runs(function() { 18 | map.setCenterZoom(new MM.Location(25, 25), 2); 19 | }); 20 | waits(600); 21 | runs(function() { 22 | var center = map.getCenter(); 23 | expect(Math.round(center.lat)).toEqual(25); 24 | expect(Math.round(center.lon)).toEqual(25); 25 | expect(map.getZoom()).toEqual(2); 26 | }); 27 | }); 28 | 29 | it('sets the right location hash', function() { 30 | runs(function() { 31 | map.setCenterZoom(new MM.Location(25, 25), 2); 32 | }); 33 | waits(1000); 34 | runs(function() { 35 | expect(window.location.hash).toEqual('#2/25.0/25.0'); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/browser/spec/Extent.js: -------------------------------------------------------------------------------- 1 | describe('Extent', function() { 2 | var ext; 3 | 4 | function Receiver() { } 5 | Receiver.prototype.receive = function() { }; 6 | 7 | beforeEach(function() { 8 | ext = new MM.Extent(-10, -10, 10, 10); 9 | 10 | }); 11 | 12 | it('properly initializes its sides', function() { 13 | expect(ext.west).toEqual(-10); 14 | expect(ext.south).toEqual(-10); 15 | expect(ext.north).toEqual(10); 16 | expect(ext.east).toEqual(10); 17 | }); 18 | 19 | it('expands to fit a location', function() { 20 | ext.encloseLocation(new MM.Location(-40, -40)); 21 | expect(ext.west).toEqual(-40); 22 | expect(ext.south).toEqual(-40); 23 | }); 24 | 25 | it('expands to fit locations', function() { 26 | ext.encloseLocations([ 27 | new MM.Location(-40, -40), 28 | new MM.Location(40, 40) 29 | ]); 30 | expect(ext.west).toEqual(-40); 31 | expect(ext.east).toEqual(40); 32 | expect(ext.south).toEqual(-40); 33 | expect(ext.north).toEqual(40); 34 | }); 35 | 36 | it('knows when it contains a location', function() { 37 | expect(ext.containsLocation(new MM.Location(0, 0))).toEqual(true); 38 | expect(ext.containsLocation(new MM.Location(0, 90))).toEqual(false); 39 | }); 40 | 41 | }); 42 | -------------------------------------------------------------------------------- /test/browser/spec/Projection.js: -------------------------------------------------------------------------------- 1 | describe('Projection', function() { 2 | var m; 3 | 4 | beforeEach(function() { 5 | m = new MM.MercatorProjection(10); 6 | }); 7 | 8 | it('can instantiate a mercator projection', function() { 9 | // TODO: row is a very small number because of odd javascript math. 10 | expect(m.locationCoordinate(new MM.Location(0, 0)).column).toEqual(0); 11 | expect(m.locationCoordinate(new MM.Location(0, 0)).zoom).toEqual(10); 12 | expect(m.coordinateLocation(new MM.Coordinate(0, 0, 10))) 13 | .toEqual(new MM.Location(0, 0)); 14 | }); 15 | 16 | it('is accurate up to 3 decimals', function() { 17 | // Confirm that these values are valid up to a 3 decimals 18 | var c2 = m.locationCoordinate(new MM.Location(37, -122)); 19 | expect(Math.round(c2.row * 1000) / 1000).toEqual(0.696); 20 | expect(Math.round(c2.column * 1000) / 1000).toEqual(-2.129); 21 | expect(c2.zoom).toEqual(10); 22 | }); 23 | 24 | it('coordinatelocation to work', function() { 25 | var l2 = m.coordinateLocation(new MM.Coordinate(0.696, -2.129, 10)); 26 | expect(Math.round(l2.lat * 1000) / 1000).toEqual(37.001); 27 | expect(Math.round(l2.lon * 1000) / 1000).toEqual(-121.983); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /examples/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Modest Maps JS 5 | 6 | 13 | 14 | 15 |

Modest Maps JS

16 |

zoom in | zoom out 17 |
pan left | pan right | pan down | pan up

18 |
19 |
20 |

The above div is a map initialized like so:

21 |
22 | // "import" the namespace
23 | var provider = new MM.MapProvider(function(coord) {
24 |     var img = parseInt(coord.zoom) +'-r'+ parseInt(coord.row) +'-c'+ parseInt(coord.column) + '.jpg';
25 |     return 'http://osm-bayarea.s3.amazonaws.com/' + img;
26 | });
27 | 
28 | map = new MM.Map('map', provider, new MM.Point(900,900), [])
29 | 
30 | map.setCenterZoom(new MM.Location(37.811530, -122.2666097), 14);
31 | 
32 |
33 | 34 | 35 | -------------------------------------------------------------------------------- /test/browser/spec/Transformation.js: -------------------------------------------------------------------------------- 1 | describe('Transformation', function() { 2 | it('can do an identity transform', function() { 3 | var t = new MM.Transformation(1, 0, 0, 0, 1, 0); 4 | var p = new MM.Point(1, 1); 5 | 6 | var p_ = t.transform(p); 7 | var p__ = t.untransform(p_); 8 | 9 | expect(p).toEqual(new MM.Point(1, 1)); 10 | expect(p_).toEqual(new MM.Point(1, 1)); 11 | expect(p__).toEqual(new MM.Point(1, 1)); 12 | }); 13 | 14 | it('can do an inverse transform', function() { 15 | var t = new MM.Transformation(0, 1, 0, 1, 0, 0); 16 | var p = new MM.Point(0, 1); 17 | 18 | var p_ = t.transform(p); 19 | var p__ = t.untransform(p_); 20 | 21 | expect(p).toEqual(new MM.Point(0, 1)); 22 | expect(p_).toEqual(new MM.Point(1, 0)); 23 | expect(p__).toEqual(new MM.Point(0, 1)); 24 | }); 25 | 26 | it('can do an addition transform', function() { 27 | var t = new MM.Transformation(1, 0, 1, 0, 1, 1); 28 | var p = new MM.Point(0, 0); 29 | 30 | var p_ = t.transform(p); 31 | var p__ = t.untransform(p_); 32 | 33 | expect(p).toEqual(new MM.Point(0, 0)); 34 | expect(p_).toEqual(new MM.Point(1, 1)); 35 | expect(p__).toEqual(new MM.Point(0, 0)); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /src/convenience.js: -------------------------------------------------------------------------------- 1 | // Instance of a map intended for drawing to a div. 2 | // 3 | // * `parent` (required DOM element) 4 | // Can also be an ID of a DOM element 5 | // * `provider` (required MM.MapProvider or URL template) 6 | // * `location` (required MM.Location) 7 | // Location for map to show 8 | // * `zoom` (required number) 9 | MM.mapByCenterZoom = function(parent, layerish, location, zoom) { 10 | var layer = MM.coerceLayer(layerish), 11 | map = new MM.Map(parent, layer, false); 12 | map.setCenterZoom(location, zoom).draw(); 13 | return map; 14 | }; 15 | 16 | // Instance of a map intended for drawing to a div. 17 | // 18 | // * `parent` (required DOM element) 19 | // Can also be an ID of a DOM element 20 | // * `provider` (required MM.MapProvider or URL template) 21 | // * `locationA` (required MM.Location) 22 | // Location of one map corner 23 | // * `locationB` (required MM.Location) 24 | // Location of other map corner 25 | MM.mapByExtent = function(parent, layerish, locationA, locationB) { 26 | var layer = MM.coerceLayer(layerish), 27 | map = new MM.Map(parent, layer, false); 28 | map.setExtent([locationA, locationB]).draw(); 29 | return map; 30 | }; 31 | -------------------------------------------------------------------------------- /test/browser/spec/DoubleClickHandler.js: -------------------------------------------------------------------------------- 1 | describe('DoubleClickHandler', function() { 2 | var map; 3 | 4 | beforeEach(function() { 5 | div = document.createElement('div'); 6 | div.id = +new Date(); 7 | div.style.width = 500; 8 | div.style.height = 500; 9 | 10 | var template = 'http://{S}tile.openstreetmap.org/{Z}/{X}/{Y}.png'; 11 | var subdomains = [ '', 'a.', 'b.', 'c.' ]; 12 | var provider = new MM.TemplatedLayer(template, subdomains); 13 | 14 | map = new MM.Map(div, provider, [ 15 | new MM.DoubleClickHandler() 16 | ]); 17 | map.setCenterZoom(new MM.Location(0, 0), 0); 18 | }); 19 | 20 | it('does not zoom in on single click', function() { 21 | expect(map.getZoom()).toEqual(0); 22 | happen.click(map.parent); 23 | expect(map.getZoom()).toEqual(0); 24 | }); 25 | 26 | it('zooms in on double click', function() { 27 | expect(map.getZoom()).toEqual(0); 28 | happen.dblclick(map.parent); 29 | expect(map.getZoom()).toEqual(1); 30 | }); 31 | 32 | it('zooms out on double click with shift', function() { 33 | map.setZoom(1); 34 | happen.dblclick(map.parent, { shift: true }); 35 | expect(map.getZoom()).toEqual(0); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /examples/placeholder/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Modest Maps JS: Placeholder tiles 5 | 6 | 17 | 26 | 27 | 28 |

Modest Maps JS: Placeholder tiles

29 |

This example uses the Placehold.it service to render image tiles that report their tile coordinates.

30 |

zoom in | zoom out 31 |
pan left | pan right | pan down | pan up

32 |
33 |
34 | 35 | 36 | -------------------------------------------------------------------------------- /test/browser/spec/DragHandler.js: -------------------------------------------------------------------------------- 1 | describe('DragHandler', function() { 2 | var map; 3 | 4 | beforeEach(function() { 5 | div = document.createElement('div'); 6 | div.id = +new Date(); 7 | div.style.width = 500; 8 | div.style.height = 500; 9 | 10 | var template = 'http://{S}tile.openstreetmap.org/{Z}/{X}/{Y}.png'; 11 | var subdomains = [ '', 'a.', 'b.', 'c.' ]; 12 | var provider = new MM.TemplatedLayer(template, subdomains); 13 | 14 | map = new MM.Map(div, provider, [ 15 | new MM.DragHandler() 16 | ]); 17 | map.setCenterZoom(new MM.Location(0, 0), 0); 18 | }); 19 | 20 | it('changes the cursor style to move while moving', function() { 21 | happen.mousedown(map.parent, { clientX: 10, clientY: 10 }); 22 | expect(map.parent.style.cursor).toEqual('move'); 23 | }); 24 | 25 | it('pan the map when you do a panning motion', function() { 26 | expect(~~map.getCenter().lat).toEqual(0); 27 | expect(~~map.getCenter().lon).toEqual(0); 28 | happen.mousedown(map.parent, { clientX: 10, clientY: 10 }); 29 | happen.mousemove(document, { clientX: 30, clientY: 30 }); 30 | happen.mouseup(document, { clientX: 30, clientY: 30 }); 31 | expect(~~map.getCenter().lat).toEqual(27); 32 | expect(~~map.getCenter().lon).toEqual(-28); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /examples/cloudmade/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Modest Maps JS 5 | 6 | 7 | 14 | 15 | 16 |

Modest Maps JS

17 |

zoom in | zoom out 18 |
pan left | pan right | pan down | pan up

19 |
20 |
21 |

The above div is a map initialized like so:

22 |
23 | // please use your own API key, see http://developers.cloudmade.com/ for more details
24 | 
25 | var provider = new MM.CloudMadeProvider('1a914755a77758e49e19a26e799268b7','998');
26 | 
27 | map = new MM.Map('map', new MM.Layer(provider), new MM.Point(640,480))
28 | 
29 | map.setCenterZoom(new MM.Location(37.804656, -122.263606), 14);
30 | 
31 |

Hands up who wants overlays?

32 |
33 | 34 | 35 | -------------------------------------------------------------------------------- /examples/anyscale/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Modest Maps JS - Any Zoom Demo 7 | 8 | 29 | 34 | 35 | 36 |
37 |
38 |
39 |
40 | 41 | 42 | -------------------------------------------------------------------------------- /examples/hash/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ModestMaps JS: Hash URLs 5 | 6 | 18 | 38 | 39 | 40 |

ModestMaps JS: Hash URLs

41 |

Go to: Stamen, null island

42 |
43 |
44 | 45 | 46 | -------------------------------------------------------------------------------- /examples/bubble/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Modest Maps JS 4 | 5 | 6 | 7 | 14 | 15 | 16 |

Modest Maps JS

17 |

zoom in | zoom out 18 |
pan left | pan right | pan down | pan up

19 |
20 |
21 |

The above div is a map initialized like so:

22 |
23 | // "import" the namespace
24 | var mm = com.modestmaps;
25 | 
26 | var layer = new mm.TemplatedLayer('http://osm-bayarea.s3.amazonaws.com/{Z}-r{Y}-c{X}.jpg');
27 | 
28 | map = new mm.Map('map', layer, new mm.Point(1024,768))
29 | 
30 | var f = new mm.Follower(map, new mm.Location(37.811530, -122.2666097), 'Broadway and Grand');
31 | 
32 | map.setCenterZoom(new mm.Location(37.811530, -122.2666097), 14);
33 | 
34 |

Hands up who wants overlays?

35 |
36 | 37 | 38 | -------------------------------------------------------------------------------- /examples/simple/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Modest Maps JS 5 | 6 | 7 | 14 | 15 | 16 |

Modest Maps JS

17 |

zoom in | zoom out 18 |
pan left | pan right | pan down | pan up

19 |
20 |
21 |

The above div is a map initialized like so:

22 |
23 | var layer = new MM.Layer(new MM.MapProvider(function(coord) {
24 |     var img = parseInt(coord.zoom) +'-r'+ parseInt(coord.row) +'-c'+ parseInt(coord.column) + '.jpg';
25 |     return 'http://osm-bayarea.s3.amazonaws.com/' + img;
26 | }));
27 | 
28 | map = new MM.Map('map', layer, new MM.Point(900,900))
29 | 
30 | var f = new MM.Follower(map, new MM.Location(37.811530, -122.2666097), '° Broadway and Grand');
31 | 
32 | map.setCenterZoom(new MM.Location(37.811530, -122.2666097), 14);
33 | 
34 |

Hands up who wants overlays?

35 |
36 | 37 | 38 | -------------------------------------------------------------------------------- /examples/templated/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Modest Maps JS 5 | 6 | 7 | 31 | 32 | 33 |

Modest Maps JS

34 |
35 | 36 | 37 |
38 |

zoom in | zoom out 39 |
pan left | pan right | pan down | pan up

40 |
41 | 42 | 43 | -------------------------------------------------------------------------------- /examples/spotlight/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ModestMaps: Spotlight Effect with HTML Canvas 5 | 6 | 7 | 28 | 34 | 35 | 36 |

Spotlight effect

37 | 38 |

The spotlights on this map are pinned to two geographic locations. Check 39 | out the Crimespotting-inspired example.

40 | 41 |
42 |
43 | 44 | 45 | -------------------------------------------------------------------------------- /examples/show-hide/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | display: none; example 5 | 6 | 32 | 52 | 53 | 54 |
55 | toggle map visibility 56 | 57 | 58 | -------------------------------------------------------------------------------- /examples/tilecache/tilecache.js: -------------------------------------------------------------------------------- 1 | MM.TileCacheMapProvider = function(template, subdomains) { 2 | 3 | // utility functions... 4 | function addZeros(i, zeros) { 5 | if (zeros === undefined) { 6 | zeros = 3; 7 | } 8 | var s = i.toString(); 9 | while (s.length < zeros) 10 | { 11 | s = '0' + s; 12 | } 13 | return s; 14 | } 15 | 16 | function tilePad(i) { 17 | return addZeros(parseInt(i / 1000000, 10)) + '/' + 18 | addZeros(parseInt(i / 1000, 10)) + '/' + addZeros(i % 1000); 19 | } 20 | 21 | // this is like a call to 'super', kinda... 22 | // we end up initializing a basic modestmaps.js MapProvider with 23 | // a getTileURL function that knows about the above utility functions 24 | MM.MapProvider.call(this, function(coord) { 25 | coord = this.sourceCoordinate(coord); 26 | if (!coord) { 27 | return null; 28 | } 29 | 30 | var mod = coord.copy(); 31 | 32 | // fill in coordinates into URL 33 | var url = template.replace('{Z}', mod.zoom) 34 | .replace('{X}', tilePad(mod.column)) 35 | .replace('{Y}', tilePad(Math.pow(2, mod.zoom) - 1 - mod.row)); 36 | // replace the {S} portion of the url with the appropriate subdomain 37 | if (url.indexOf('{S}') > -1) { 38 | var subdomain = (subdomains && subdomains.length > 0) ? 39 | subdomains[parseInt(mod.row + mod.column, 10) % subdomains.length] : ''; 40 | url = url.replace('{S}', subdomain ? subdomain + '.' : ''); 41 | } 42 | 43 | return url; 44 | }); 45 | }; 46 | 47 | MM.extend(MM.TileCacheMapProvider, MM.MapProvider); 48 | -------------------------------------------------------------------------------- /test/browser/lib/happen.js: -------------------------------------------------------------------------------- 1 | !(function(context) { 2 | var h = {}; 3 | 4 | // Make inheritance bearable: clone one level of properties 5 | function extend(child, parent) { 6 | for (var property in parent) { 7 | if (typeof child[property] == 'undefined') { 8 | child[property] = parent[property]; 9 | } 10 | } 11 | return child; 12 | } 13 | 14 | h.once = function(x, o) { 15 | var evt = document.createEvent('MouseEvents'); 16 | // https://developer.mozilla.org/en/DOM/event.initMouseEvent 17 | evt.initMouseEvent(o.type, 18 | true, // canBubble 19 | true, // cancelable 20 | window, // 'AbstractView' 21 | o.detail || 0, // click count 22 | o.screenX || 0, // screenX 23 | o.screenY || 0, // screenY 24 | o.clientX || 0, // clientX 25 | o.clientY || 0, // clientY 26 | o.ctrl || 0, // ctrl 27 | o.alt || false, // alt 28 | o.shift || false, // shift 29 | o.meta || false, // meta 30 | o.button || false, // mouse button 31 | null // relatedTarget 32 | ); 33 | x.dispatchEvent(evt); 34 | }; 35 | 36 | var shortcuts = ['click', 'mousedown', 'mouseup', 'mousemove'], 37 | s, i = 0; 38 | 39 | while (s = shortcuts[i++]) { 40 | h[s] = (function(s) { 41 | return function(x, o) { 42 | h.once(x, extend(o || {}, { type: s })); 43 | }; 44 | })(s); 45 | } 46 | 47 | h.dblclick = function(x, o) { 48 | h.once(x, extend(o || {}, { 49 | type: 'dblclick', 50 | detail: 2 51 | })); 52 | }; 53 | 54 | this.happen = h; 55 | })(this); 56 | -------------------------------------------------------------------------------- /examples/touch/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Modest Maps JS - Touch Tester 8 | 9 | 24 | 49 | 50 | 51 |
52 | 53 | 54 | -------------------------------------------------------------------------------- /examples/greatcircle/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Modest Maps JS - Great Circle Test 5 | 6 | 58 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /examples/navwindow/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Modest Maps JS - Nav Window Test 5 | 6 | 43 | 70 | 71 | 72 |
73 | 77 |
78 | 79 | 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | _ _ _ 2 | | | | | (_) 3 | _ __ ___ ___ __| | ___ ___| |_ _ __ ___ __ _ _ __ ___ _ ___ 4 | | '_ ` _ \ / _ \ / _` |/ _ \/ __| __| | '_ ` _ \ / _` | '_ \/ __| | / __| 5 | | | | | | | (_) | (_| | __/\__ \ |_ | | | | | | (_| | |_) \__ \ | \__ \ 6 | |_| |_| |_|\___/ \__,_|\___||___/\__| |_| |_| |_|\__,_| .__/|___/ | |___/ 7 | | | _/ | 8 | |_| |__/ 9 | 10 | Modest Maps JS is a BSD-licensed display and interaction library for tile-based 11 | maps in Javascript. 12 | 13 | Our intent is to provide a minimal, extensible, customizable, and free display 14 | library for discriminating designers and developers who want to use interactive 15 | maps in their own projects. Modest Maps provides a core set of features in a 16 | tight, clean package, with plenty of hooks for additional functionality. 17 | 18 | Though Modest Maps JS is in its infancy it's derived from our trusty Python and 19 | Actionscript code that has served us well for years. The best place to see it 20 | in action today is Walking Papers, at http://walkingpapers.org 21 | 22 | # Usage: 23 | 24 | See `examples/` and [the wiki home page](https://github.com/stamen/modestmaps-js/wiki) 25 | for ideas on how to start out using Modest Maps. 26 | 27 | # Building 28 | 29 | This package includes a copy of [YUICompressor](http://developer.yahoo.com/yui/compressor/), 30 | which requires a version of Java on your system. To create a new build of 31 | Modest Maps (only necessary for development), run `make` from the root 32 | directory. 33 | 34 | # Developing with npm: 35 | 36 | Modest Maps includes a `package.json` file to guide usage of its code on the 37 | server-side, and to handle certain dependencies. 38 | 39 | To install developer dependencies - needed for documentation and tests - 40 | you'll need [npm](http://npmjs.org/): 41 | 42 | npm install --dev 43 | 44 | # Tests 45 | 46 | Tests require `expresso` to be installed by `npm`, as noted above. To run tests, 47 | 48 | make tests 49 | -------------------------------------------------------------------------------- /examples/simple/follower.js: -------------------------------------------------------------------------------- 1 | MM.Follower = function(map, location, content, dimensions) { 2 | this.coord = map.locationCoordinate(location); 3 | this.offset = new com.modestmaps.Point(0, 0); 4 | this.dimensions = dimensions || new com.modestmaps.Point(100, 50); 5 | this.padding = new com.modestmaps.Point(10, 10); 6 | this.offset = new com.modestmaps.Point(0, -this.dimensions.y); 7 | 8 | var follower = this; 9 | var callback = function(m, a) { return follower.draw(m); }; 10 | map.addCallback('drawn', callback); 11 | this.div = document.createElement('div'); 12 | this.div.style.position = 'absolute'; 13 | this.div.style.width = this.dimensions.x + 'px'; 14 | this.div.style.height = this.dimensions.y + 'px'; 15 | //this.div.style.backgroundColor = 'white'; 16 | //this.div.style.border = 'solid black 1px'; 17 | this.div.innerHTML = content; 18 | MM.addEvent(this.div, 'mousedown', function(e) { 19 | if(!e) e = window.event; 20 | return com.modestmaps.cancelEvent(e); 21 | }); 22 | map.parent.appendChild(this.div); 23 | this.draw(map); 24 | }; 25 | 26 | MM.Follower.prototype = { 27 | 28 | div: null, 29 | coord: null, 30 | offset: null, 31 | dimensions: null, 32 | padding: null, 33 | 34 | draw: function(map) { 35 | var point; 36 | try { 37 | point = map.coordinatePoint(this.coord); 38 | } catch(e) { 39 | // too soon? 40 | return; 41 | } 42 | if (point.x + this.dimensions.x + this.offset.x < 0) { 43 | // too far left 44 | this.div.style.display = 'none'; 45 | } else if(point.y + this.dimensions.y + this.offset.y < 0) { 46 | // too far up 47 | this.div.style.display = 'none'; 48 | } else if(point.x + this.offset.x > map.dimensions.x) { 49 | // too far right 50 | this.div.style.display = 'none'; 51 | } else if(point.y + this.offset.y > map.dimensions.y) { 52 | // too far down 53 | this.div.style.display = 'none'; 54 | } else { 55 | this.div.style.display = 'block'; 56 | this.div.style.left = point.x + this.offset.x + 'px'; 57 | this.div.style.top = point.y + this.offset.y + 'px'; 58 | } 59 | } 60 | }; 61 | -------------------------------------------------------------------------------- /examples/flickr/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Modest Maps JS 5 | 6 | 7 | 8 | 16 | 17 | 18 |

Modest Maps JS

19 |

zoom in | zoom out 20 |
pan left | pan right | pan down | pan up

21 |
22 |
23 |

The above div is a map initialized like so:

24 |
25 | // "import" the namespace
26 | var MM = com.modestmaps;
27 | 
28 | var provider = new MM.Layer(new MM.BlueMarbleProvider());
29 | 
30 | map = new MM.Map('map', provider, new MM.Point(1024,512))
31 | 
32 | map.setCenterZoom(new MM.Location(20.0, 0), 2);
33 | 
34 | window.jsonFlickrApi = function(rsp) {
35 |     var photos = rsp.photos.photo;
36 |     for (var i = 0; i < photos.length; i++) {
37 |         var p = photos[i];
38 |         var url = [ 'http://farm', p.farm, '.static.flickr.com/', p.server, '/', p.id, '_', p.secret, '_s.jpg' ].join('');    
39 |         var html = "<" + "img src='" + url + "'" + ">"; // weird for 'eval', sorry
40 |         var location = new MM.Location(p.latitude, p.longitude);
41 |         var dimensions = new MM.Point(75, 75);
42 |         var f = new MM.Follower(map, location, html, dimensions);
43 |     }
44 | }
45 | 
46 | var script = document.createElement('script');
47 | script.src = 'http://api.flickr.com/services/rest/?'+
48 |                'method=flickr.photos.search&'+
49 |                'api_key=a96cbef093a8c0280d3aed4e0c004d4c&'+
50 |                'tags=clouds&'+
51 |                'extras=geo&'+
52 |                'has_geo=1&'+
53 |                'format=json';
54 | document.getElementsByTagName('head')[0].appendChild(script);
55 | 
56 | 
57 |

Hands up who wants overlays?

58 |
59 | 60 | 61 | -------------------------------------------------------------------------------- /examples/enforce-limits/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Modest Maps JS 5 | 6 | 48 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /src/callbacks.js: -------------------------------------------------------------------------------- 1 | // CallbackManager 2 | // --------------- 3 | // A general-purpose event binding manager used by `Map` 4 | // and `RequestManager` 5 | 6 | // Construct a new CallbackManager, with an list of 7 | // supported events. 8 | MM.CallbackManager = function(owner, events) { 9 | this.owner = owner; 10 | this.callbacks = {}; 11 | for (var i = 0; i < events.length; i++) { 12 | this.callbacks[events[i]] = []; 13 | } 14 | }; 15 | 16 | // CallbackManager does simple event management for modestmaps 17 | MM.CallbackManager.prototype = { 18 | // The element on which callbacks will be triggered. 19 | owner: null, 20 | 21 | // An object of callbacks in the form 22 | // 23 | // { event: function } 24 | callbacks: null, 25 | 26 | // Add a callback to this object - where the `event` is a string of 27 | // the event name and `callback` is a function. 28 | addCallback: function(event, callback) { 29 | if (typeof(callback) == 'function' && this.callbacks[event]) { 30 | this.callbacks[event].push(callback); 31 | } 32 | }, 33 | 34 | // Remove a callback. The given function needs to be equal (`===`) to 35 | // the callback added in `addCallback`, so named functions should be 36 | // used as callbacks. 37 | removeCallback: function(event, callback) { 38 | if (typeof(callback) == 'function' && this.callbacks[event]) { 39 | var cbs = this.callbacks[event], 40 | len = cbs.length; 41 | for (var i = 0; i < len; i++) { 42 | if (cbs[i] === callback) { 43 | cbs.splice(i,1); 44 | break; 45 | } 46 | } 47 | } 48 | }, 49 | 50 | // Trigger a callback, passing it an object or string from the second 51 | // argument. 52 | dispatchCallback: function(event, message) { 53 | if(this.callbacks[event]) { 54 | for (var i = 0; i < this.callbacks[event].length; i += 1) { 55 | try { 56 | this.callbacks[event][i](this.owner, message); 57 | } catch(e) { 58 | //console.log(e); 59 | // meh 60 | } 61 | } 62 | } 63 | } 64 | }; 65 | -------------------------------------------------------------------------------- /examples/layers/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Modest Maps JS - Layer Sample 4 | 5 | 34 | 44 | 45 | 46 |
47 |
48 |

49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 |

57 | 58 | 59 | -------------------------------------------------------------------------------- /test/browser/index.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | Jasmine Spec Runner 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /examples/hurricanes/mapcontrols-raphael.js: -------------------------------------------------------------------------------- 1 | // namespacing! 2 | if (!com) { 3 | var com = { }; 4 | if (!com.modestmaps) { 5 | com.modestmaps = { }; 6 | } 7 | } 8 | 9 | com.modestmaps.MapControls = function(map) 10 | { 11 | // get your div on 12 | 13 | this.div = document.createElement('div'); 14 | this.div.style.position = 'absolute'; 15 | this.div.style.left = '0px'; 16 | this.div.style.top = '0px'; 17 | map.parent.appendChild(this.div); 18 | 19 | this.canvas = Raphael(this.div, 200, 100); 20 | 21 | var left = this.canvas.circle(15, 30, 10).attr("fill", "red"); 22 | var lefta = this.canvas.path({fill: "white"}, "M -6 0 L 3 -5 L 3 5").translate(15, 30); 23 | var down = this.canvas.circle(40, 45, 10).attr("fill", "red"); 24 | var downa = this.canvas.path({fill: "white"}, "M 0 6 L -5 -3 L 5 -3").translate(40, 45); 25 | var right = this.canvas.circle(65, 30, 10).attr("fill", "red"); 26 | var righta = this.canvas.path({fill: "white"}, "M 6 0 L -3 -5 L -3 5").translate(65, 30); 27 | var up = this.canvas.circle(40, 15, 10).attr("fill", "red"); 28 | var upa = this.canvas.path({fill: "white"}, "M 0 -6 L -5 3 L 5 3").translate(40, 15); 29 | var zin = this.canvas.circle(95, 15, 10).attr("fill", "red"); 30 | var zina = this.canvas.path({stroke: "white", 'stroke-width': 2}, "M -5 0 L 5 0 M 0 -5 L 0 5").translate(95, 15); 31 | var zout = this.canvas.circle(95, 45, 10).attr("fill", "red"); 32 | var zouta = this.canvas.path({stroke: "white", 'stroke-width': 2}, "M -5 0 L 5 0").translate(95, 45); 33 | 34 | lefta.node.onclick = left.node.onclick = function() { map.panLeft() }; 35 | righta.node.onclick = right.node.onclick = function() { map.panRight() }; 36 | upa.node.onclick = up.node.onclick = function() { map.panUp() }; 37 | downa.node.onclick = down.node.onclick = function() { map.panDown() }; 38 | zina.node.onclick = zin.node.onclick = function() { map.zoomIn() }; 39 | zouta.node.onclick = zout.node.onclick = function() { map.zoomOut() }; 40 | 41 | lefta.node.style.cursor = left.node.style.cursor = 'pointer'; 42 | righta.node.style.cursor = right.node.style.cursor = 'pointer'; 43 | upa.node.style.cursor = up.node.style.cursor = 'pointer'; 44 | downa.node.style.cursor = down.node.style.cursor = 'pointer'; 45 | zina.node.style.cursor = zin.node.style.cursor = 'pointer'; 46 | zouta.node.style.cursor = zout.node.style.cursor = 'pointer'; 47 | 48 | }; 49 | 50 | com.modestmaps.MapControls.prototype = { 51 | 52 | div: null, 53 | canvas: null 54 | 55 | }; -------------------------------------------------------------------------------- /examples/microsoft/bing.js: -------------------------------------------------------------------------------- 1 | // namespacing! 2 | if (!MM) { 3 | MM = { }; 4 | } 5 | 6 | MM.BingProvider = function(key, style, onready) { 7 | 8 | this.key = key; 9 | this.style = style; 10 | 11 | // hit the imagery metadata service 12 | // http://msdn.microsoft.com/en-us/library/ff701716.aspx 13 | 14 | // Aerial, AerialWithLabels, Road 15 | var script = document.createElement('script'); 16 | script.type = 'text/javascript'; 17 | document.getElementsByTagName('head')[0].appendChild(script); 18 | script.src = 'http://dev.virtualearth.net/REST/V1/Imagery/Metadata/'+style+'/?key='+key+'&jsonp=onBingComplete'; 19 | 20 | function toMicrosoft(column, row, zoom) { 21 | // generate zoom string by interleaving row/col bits 22 | // NB:- this assumes you're only asking for positive row/cols 23 | var quadKey = ""; 24 | for (var i = 1; i <= zoom; i++) { 25 | var rowBit = (row >> zoom-i) & 1; 26 | var colBit = (column >> zoom-i) & 1; 27 | quadKey += (rowBit << 1) + colBit; 28 | } 29 | return quadKey; 30 | } 31 | 32 | var provider = this; 33 | 34 | window.onBingComplete = function(data) { 35 | var resourceSets = data.resourceSets; 36 | for (var i = 0; i < resourceSets.length; i++) { 37 | var resources = data.resourceSets[i].resources; 38 | for (var j = 0; j < resources.length; j++) { 39 | var resource = resources[j]; 40 | 41 | var serverSalt = Math.floor(Math.random() * 4); 42 | provider.getTile = function(coord) { 43 | var quadKey = toMicrosoft(coord.column, coord.row, coord.zoom); 44 | // this is so that requests will be consistent in this session, rather than totally random 45 | var server = Math.abs(serverSalt + coord.column + coord.row + coord.zoom) % 4; 46 | return resource.imageUrl 47 | .replace('{quadkey}',quadKey) 48 | .replace('{subdomain}', resource.imageUrlSubdomains[server]); 49 | }; 50 | // TODO: use resource.imageWidth 51 | // TODO: use resource.imageHeight 52 | } 53 | } 54 | 55 | // TODO: display data.brandLogoUri 56 | // TODO: display data.copyright 57 | onready(provider); 58 | }; 59 | }; 60 | 61 | MM.BingProvider.prototype = { 62 | key: null, 63 | style: null, 64 | subdomains: null, 65 | getTileUrl: null 66 | }; 67 | 68 | MM.extend(MM.BingProvider, MM.MapProvider); 69 | -------------------------------------------------------------------------------- /examples/sprite-tiles/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ModestMaps JS: Sprite Tiles 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 42 | 69 | 70 | 71 |

ModestMaps JS: Sprite Tiles

72 |
73 |

+1ft sea level rise & storm surge

74 |
75 |
76 | 77 | 78 | -------------------------------------------------------------------------------- /examples/flickr/follower.js: -------------------------------------------------------------------------------- 1 | // namespacing! 2 | if (!com) { 3 | var com = { }; 4 | if (!com.modestmaps) { 5 | com.modestmaps = { }; 6 | } 7 | } 8 | 9 | (function(MM){ 10 | MM.Follower = function(map, location, content, dimensions) { 11 | this.coord = map.locationCoordinate(location); 12 | 13 | this.offset = new MM.Point(0, 0); 14 | this.dimensions = dimensions || new MM.Point(100, 50); 15 | this.padding = new MM.Point(10, 10); 16 | this.offset = new MM.Point(0, -this.dimensions.y); 17 | 18 | var follower = this; 19 | 20 | var callback = function(m, a) { return follower.draw(m); }; 21 | map.addCallback('drawn', callback); 22 | 23 | this.div = document.createElement('div'); 24 | this.div.style.position = 'absolute'; 25 | this.div.style.width = this.dimensions.x + 'px'; 26 | this.div.style.height = this.dimensions.y + 'px'; 27 | //this.div.style.backgroundColor = 'white'; 28 | //this.div.style.border = 'solid black 1px'; 29 | 30 | this.div.innerHTML = content; 31 | 32 | MM.addEvent(this.div, 'mousedown', function(e) { 33 | if(!e) e = window.event; 34 | return MM.cancelEvent(e); 35 | }); 36 | 37 | map.parent.appendChild(this.div); 38 | 39 | this.draw(map); 40 | } 41 | 42 | MM.Follower.prototype = { 43 | 44 | div: null, 45 | coord: null, 46 | 47 | offset: null, 48 | dimensions: null, 49 | padding: null, 50 | 51 | draw: function(map) 52 | { 53 | try { 54 | var point = map.coordinatePoint(this.coord); 55 | 56 | } catch(e) { 57 | // too soon? 58 | return; 59 | } 60 | 61 | if(point.x + this.dimensions.x + this.offset.x < 0) { 62 | // too far left 63 | this.div.style.display = 'none'; 64 | 65 | } else if(point.y + this.dimensions.y + this.offset.y < 0) { 66 | // too far up 67 | this.div.style.display = 'none'; 68 | 69 | } else if(point.x + this.offset.x > map.dimensions.x) { 70 | // too far right 71 | this.div.style.display = 'none'; 72 | 73 | } else if(point.y + this.offset.y > map.dimensions.y) { 74 | // too far down 75 | this.div.style.display = 'none'; 76 | 77 | } else { 78 | this.div.style.display = 'block'; 79 | MM.moveElement(this.div, { 80 | x: Math.round(point.x + this.offset.x), 81 | y: Math.round(point.y + this.offset.y), 82 | scale: 1, 83 | width: this.dimensions.x, 84 | height: this.dimensions.y 85 | }); 86 | } 87 | } 88 | 89 | }; 90 | 91 | })(com.modestmaps); 92 | -------------------------------------------------------------------------------- /examples/templated/follower.js: -------------------------------------------------------------------------------- 1 | // namespacing! 2 | if (!com) { 3 | var com = { }; 4 | if (!com.modestmaps) { 5 | com.modestmaps = { }; 6 | } 7 | } 8 | 9 | com.modestmaps.Follower = function(map, location, content, dimensions) 10 | { 11 | this.coord = map.locationCoordinate(location); 12 | 13 | this.offset = new com.modestmaps.Point(0, 0); 14 | this.dimensions = dimensions || new com.modestmaps.Point(100, 50); 15 | this.padding = new com.modestmaps.Point(10, 10); 16 | this.offset = new com.modestmaps.Point(0, -this.dimensions.y); 17 | 18 | var follower = this; 19 | 20 | var callback = function(m, a) { return follower.draw(m); }; 21 | map.addCallback('panned', callback); 22 | map.addCallback('zoomed', callback); 23 | map.addCallback('centered', callback); 24 | map.addCallback('extentset', callback); 25 | map.addCallback('resized', callback); 26 | 27 | this.div = document.createElement('div'); 28 | this.div.style.position = 'absolute'; 29 | this.div.style.width = this.dimensions.x + 'px'; 30 | this.div.style.height = this.dimensions.y + 'px'; 31 | //this.div.style.backgroundColor = 'white'; 32 | //this.div.style.border = 'solid black 1px'; 33 | 34 | this.div.innerHTML = content; 35 | 36 | com.modestmaps.addEvent(this.div, 'mousedown', function(e) { 37 | if(!e) e = window.event; 38 | return com.modestmaps.cancelEvent(e); 39 | }); 40 | 41 | map.parent.appendChild(this.div); 42 | 43 | this.draw(map); 44 | } 45 | 46 | com.modestmaps.Follower.prototype = { 47 | 48 | div: null, 49 | coord: null, 50 | 51 | offset: null, 52 | dimensions: null, 53 | padding: null, 54 | 55 | draw: function(map) 56 | { 57 | try { 58 | var point = map.coordinatePoint(this.coord); 59 | 60 | } catch(e) { 61 | // too soon? 62 | return; 63 | } 64 | 65 | if(point.x + this.dimensions.x + this.offset.x < 0) { 66 | // too far left 67 | this.div.style.display = 'none'; 68 | 69 | } else if(point.y + this.dimensions.y + this.offset.y < 0) { 70 | // too far up 71 | this.div.style.display = 'none'; 72 | 73 | } else if(point.x + this.offset.x > map.dimensions.x) { 74 | // too far right 75 | this.div.style.display = 'none'; 76 | 77 | } else if(point.y + this.offset.y > map.dimensions.y) { 78 | // too far down 79 | this.div.style.display = 'none'; 80 | 81 | } else { 82 | this.div.style.display = 'block'; 83 | this.div.style.left = point.x + this.offset.x + 'px'; 84 | this.div.style.top = point.y + this.offset.y + 'px'; 85 | } 86 | } 87 | 88 | }; 89 | -------------------------------------------------------------------------------- /examples/cabs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Modest Maps JS 4 | 5 | 6 | 7 | 65 | 66 | 67 |

Cabspotting JS

68 |

zoom in | zoom out 69 |
pan left | pan right | pan down | pan up

70 |
71 |
72 | 73 | 74 | -------------------------------------------------------------------------------- /test/browser/lib/jasmine-1.1.0.rc1/jasmine.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; 3 | } 4 | 5 | 6 | .jasmine_reporter a:visited, .jasmine_reporter a { 7 | color: #303; 8 | } 9 | 10 | .jasmine_reporter a:hover, .jasmine_reporter a:active { 11 | color: blue; 12 | } 13 | 14 | .run_spec { 15 | float:right; 16 | padding-right: 5px; 17 | font-size: .8em; 18 | text-decoration: none; 19 | } 20 | 21 | .jasmine_reporter { 22 | margin: 0 5px; 23 | } 24 | 25 | .banner { 26 | color: #303; 27 | background-color: #fef; 28 | padding: 5px; 29 | } 30 | 31 | .logo { 32 | float: left; 33 | font-size: 1.1em; 34 | padding-left: 5px; 35 | } 36 | 37 | .logo .version { 38 | font-size: .6em; 39 | padding-left: 1em; 40 | } 41 | 42 | .runner.running { 43 | background-color: yellow; 44 | } 45 | 46 | 47 | .options { 48 | text-align: right; 49 | font-size: .8em; 50 | } 51 | 52 | 53 | 54 | 55 | .suite { 56 | border: 1px outset gray; 57 | margin: 5px 0; 58 | padding-left: 1em; 59 | } 60 | 61 | .suite .suite { 62 | margin: 5px; 63 | } 64 | 65 | .suite.passed { 66 | background-color: #dfd; 67 | } 68 | 69 | .suite.failed { 70 | background-color: #fdd; 71 | } 72 | 73 | .spec { 74 | margin: 5px; 75 | padding-left: 1em; 76 | clear: both; 77 | } 78 | 79 | .spec.failed, .spec.passed, .spec.skipped { 80 | padding-bottom: 5px; 81 | border: 1px solid gray; 82 | } 83 | 84 | .spec.failed { 85 | background-color: #fbb; 86 | border-color: red; 87 | } 88 | 89 | .spec.passed { 90 | background-color: #bfb; 91 | border-color: green; 92 | } 93 | 94 | .spec.skipped { 95 | background-color: #bbb; 96 | } 97 | 98 | .messages { 99 | border-left: 1px dashed gray; 100 | padding-left: 1em; 101 | padding-right: 1em; 102 | } 103 | 104 | .passed { 105 | background-color: #cfc; 106 | display: none; 107 | } 108 | 109 | .failed { 110 | background-color: #fbb; 111 | } 112 | 113 | .skipped { 114 | color: #777; 115 | background-color: #eee; 116 | display: none; 117 | } 118 | 119 | 120 | /*.resultMessage {*/ 121 | /*white-space: pre;*/ 122 | /*}*/ 123 | 124 | .resultMessage span.result { 125 | display: block; 126 | line-height: 2em; 127 | color: black; 128 | } 129 | 130 | .resultMessage .mismatch { 131 | color: black; 132 | } 133 | 134 | .stackTrace { 135 | white-space: pre; 136 | font-size: .8em; 137 | margin-left: 10px; 138 | max-height: 5em; 139 | overflow: auto; 140 | border: 1px inset red; 141 | padding: 1em; 142 | background: #eef; 143 | } 144 | 145 | .finished-at { 146 | padding-left: 1em; 147 | font-size: .6em; 148 | } 149 | 150 | .show-passed .passed, 151 | .show-skipped .skipped { 152 | display: block; 153 | } 154 | 155 | 156 | #jasmine_content { 157 | position:fixed; 158 | right: 100%; 159 | } 160 | 161 | .runner { 162 | border: 1px solid gray; 163 | display: block; 164 | margin: 5px 0; 165 | padding: 2px 0 2px 10px; 166 | } 167 | -------------------------------------------------------------------------------- /src/transformation.js: -------------------------------------------------------------------------------- 1 | // Transformation 2 | // -------------- 3 | MM.Transformation = function(ax, bx, cx, ay, by, cy) { 4 | this.ax = ax; 5 | this.bx = bx; 6 | this.cx = cx; 7 | this.ay = ay; 8 | this.by = by; 9 | this.cy = cy; 10 | }; 11 | 12 | MM.Transformation.prototype = { 13 | 14 | ax: 0, 15 | bx: 0, 16 | cx: 0, 17 | ay: 0, 18 | by: 0, 19 | cy: 0, 20 | 21 | transform: function(point) { 22 | return new MM.Point(this.ax * point.x + this.bx * point.y + this.cx, 23 | this.ay * point.x + this.by * point.y + this.cy); 24 | }, 25 | 26 | untransform: function(point) { 27 | return new MM.Point((point.x * this.by - point.y * this.bx - 28 | this.cx * this.by + this.cy * this.bx) / 29 | (this.ax * this.by - this.ay * this.bx), 30 | (point.x * this.ay - point.y * this.ax - 31 | this.cx * this.ay + this.cy * this.ax) / 32 | (this.bx * this.ay - this.by * this.ax)); 33 | } 34 | 35 | }; 36 | 37 | 38 | // Generates a transform based on three pairs of points, 39 | // a1 -> a2, b1 -> b2, c1 -> c2. 40 | MM.deriveTransformation = function(a1x, a1y, a2x, a2y, 41 | b1x, b1y, b2x, b2y, 42 | c1x, c1y, c2x, c2y) { 43 | var x = MM.linearSolution(a1x, a1y, a2x, 44 | b1x, b1y, b2x, 45 | c1x, c1y, c2x); 46 | var y = MM.linearSolution(a1x, a1y, a2y, 47 | b1x, b1y, b2y, 48 | c1x, c1y, c2y); 49 | return new MM.Transformation(x[0], x[1], x[2], y[0], y[1], y[2]); 50 | }; 51 | 52 | // Solves a system of linear equations. 53 | // 54 | // t1 = (a * r1) + (b + s1) + c 55 | // t2 = (a * r2) + (b + s2) + c 56 | // t3 = (a * r3) + (b + s3) + c 57 | // 58 | // r1 - t3 are the known values. 59 | // a, b, c are the unknowns to be solved. 60 | // returns the a, b, c coefficients. 61 | MM.linearSolution = function(r1, s1, t1, r2, s2, t2, r3, s3, t3) { 62 | // make them all floats 63 | r1 = parseFloat(r1); 64 | s1 = parseFloat(s1); 65 | t1 = parseFloat(t1); 66 | r2 = parseFloat(r2); 67 | s2 = parseFloat(s2); 68 | t2 = parseFloat(t2); 69 | r3 = parseFloat(r3); 70 | s3 = parseFloat(s3); 71 | t3 = parseFloat(t3); 72 | 73 | var a = (((t2 - t3) * (s1 - s2)) - ((t1 - t2) * (s2 - s3))) / 74 | (((r2 - r3) * (s1 - s2)) - ((r1 - r2) * (s2 - s3))); 75 | 76 | var b = (((t2 - t3) * (r1 - r2)) - ((t1 - t2) * (r2 - r3))) / 77 | (((s2 - s3) * (r1 - r2)) - ((s1 - s2) * (r2 - r3))); 78 | 79 | var c = t1 - (r1 * a) - (s1 * b); 80 | return [ a, b, c ]; 81 | }; 82 | -------------------------------------------------------------------------------- /examples/cabs/cab-follower.js: -------------------------------------------------------------------------------- 1 | // namespacing! 2 | if (!com) { 3 | var com = { }; 4 | if (!com.modestmaps) { 5 | com.modestmaps = { }; 6 | } 7 | } 8 | 9 | com.modestmaps.CabFollower = function(map, location, content) 10 | { 11 | this.coord = map.locationCoordinate(location); 12 | 13 | this.dimensions = new com.modestmaps.Point(20, 20); 14 | this.offset = new com.modestmaps.Point(this.dimensions.x/2, -this.dimensions.y/2); 15 | 16 | var follower = this; 17 | 18 | var callback = function(m, a) { return follower.draw(m); }; 19 | map.addCallback('panned', callback); 20 | map.addCallback('zoomed', callback); 21 | map.addCallback('centered', callback); 22 | map.addCallback('extentset', callback); 23 | 24 | this.div = document.createElement('div'); 25 | this.div.style.position = 'absolute'; 26 | this.div.style.width = this.dimensions.x + 'px'; 27 | this.div.style.height = this.dimensions.y + 'px'; 28 | 29 | var circle = document.createElement('canvas'); 30 | this.div.appendChild(circle); 31 | if (typeof G_vmlCanvasManager !== 'undefined') circle = G_vmlCanvasManager.initElement(circle); 32 | 33 | circle.style.position = 'absolute'; 34 | circle.style.left = '0px'; 35 | circle.style.top = '0px'; 36 | circle.width = this.dimensions.x; 37 | circle.height = this.dimensions.y; 38 | var ctx = circle.getContext("2d"); 39 | ctx.lineWidth = 3; 40 | ctx.strokeStyle = "rgba(255,255,0,1)"; 41 | ctx.fillStyle = "rgba(0,0,0,1)"; 42 | ctx.beginPath(); 43 | ctx.arc(this.dimensions.x/2, this.dimensions.x/2, -2+this.dimensions.x/2, 0, Math.PI*2, true); 44 | ctx.closePath(); 45 | ctx.fill(); 46 | ctx.stroke(); 47 | 48 | map.parent.appendChild(this.div); 49 | 50 | this.draw(map); 51 | } 52 | 53 | com.modestmaps.CabFollower.prototype = { 54 | 55 | div: null, 56 | coord: null, 57 | 58 | offset: null, 59 | dimensions: null, 60 | margin: null, 61 | 62 | draw: function(map) 63 | { 64 | try { 65 | var point = map.coordinatePoint(this.coord); 66 | 67 | } catch(e) { 68 | // too soon? 69 | return; 70 | } 71 | 72 | if(point.x + this.dimensions.x + this.offset.x < 0) { 73 | // too far left 74 | this.div.style.display = 'none'; 75 | 76 | } else if(point.y + this.dimensions.y + this.offset.y < 0) { 77 | // too far up 78 | this.div.style.display = 'none'; 79 | 80 | } else if(point.x + this.offset.x > map.dimensions.x) { 81 | // too far right 82 | this.div.style.display = 'none'; 83 | 84 | } else if(point.y + this.offset.y > map.dimensions.y) { 85 | // too far down 86 | this.div.style.display = 'none'; 87 | 88 | } else { 89 | this.div.style.display = 'block'; 90 | this.div.style.left = point.x + this.offset.x + 'px'; 91 | this.div.style.top = point.y + this.offset.y + 'px'; 92 | } 93 | }, 94 | 95 | }; 96 | -------------------------------------------------------------------------------- /examples/twomaps/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Modest Maps JS 5 | 6 | 69 | 70 | 71 |

Modest Maps JS

72 |

zoom in | zoom out 73 |
pan left | pan right | pan down | pan up

74 |
75 |
76 |
77 |
78 | 79 | 80 | -------------------------------------------------------------------------------- /src/projection.js: -------------------------------------------------------------------------------- 1 | // Projection 2 | // ---------- 3 | 4 | // An abstract class / interface for projections 5 | MM.Projection = function(zoom, transformation) { 6 | if (!transformation) { 7 | transformation = new MM.Transformation(1, 0, 0, 0, 1, 0); 8 | } 9 | this.zoom = zoom; 10 | this.transformation = transformation; 11 | }; 12 | 13 | MM.Projection.prototype = { 14 | 15 | zoom: 0, 16 | transformation: null, 17 | 18 | rawProject: function(point) { 19 | throw "Abstract method rawProject not implemented by subclass."; 20 | }, 21 | 22 | rawUnproject: function(point) { 23 | throw "Abstract method rawUnproject not implemented by subclass."; 24 | }, 25 | 26 | project: function(point) { 27 | point = this.rawProject(point); 28 | if(this.transformation) { 29 | point = this.transformation.transform(point); 30 | } 31 | return point; 32 | }, 33 | 34 | unproject: function(point) { 35 | if(this.transformation) { 36 | point = this.transformation.untransform(point); 37 | } 38 | point = this.rawUnproject(point); 39 | return point; 40 | }, 41 | 42 | locationCoordinate: function(location) { 43 | var point = new MM.Point(Math.PI * location.lon / 180.0, 44 | Math.PI * location.lat / 180.0); 45 | point = this.project(point); 46 | return new MM.Coordinate(point.y, point.x, this.zoom); 47 | }, 48 | 49 | coordinateLocation: function(coordinate) { 50 | coordinate = coordinate.zoomTo(this.zoom); 51 | var point = new MM.Point(coordinate.column, coordinate.row); 52 | point = this.unproject(point); 53 | return new MM.Location(180.0 * point.y / Math.PI, 54 | 180.0 * point.x / Math.PI); 55 | } 56 | }; 57 | 58 | // A projection for equilateral maps, based on longitude and latitude 59 | MM.LinearProjection = function(zoom, transformation) { 60 | MM.Projection.call(this, zoom, transformation); 61 | }; 62 | 63 | // The Linear projection doesn't reproject points 64 | MM.LinearProjection.prototype = { 65 | rawProject: function(point) { 66 | return new MM.Point(point.x, point.y); 67 | }, 68 | rawUnproject: function(point) { 69 | return new MM.Point(point.x, point.y); 70 | } 71 | }; 72 | 73 | MM.extend(MM.LinearProjection, MM.Projection); 74 | 75 | MM.MercatorProjection = function(zoom, transformation) { 76 | // super! 77 | MM.Projection.call(this, zoom, transformation); 78 | }; 79 | 80 | // Project lon/lat points into meters required for Mercator 81 | MM.MercatorProjection.prototype = { 82 | rawProject: function(point) { 83 | return new MM.Point(point.x, 84 | Math.log(Math.tan(0.25 * Math.PI + 0.5 * point.y))); 85 | }, 86 | 87 | rawUnproject: function(point) { 88 | return new MM.Point(point.x, 89 | 2 * Math.atan(Math.pow(Math.E, point.y)) - 0.5 * Math.PI); 90 | } 91 | }; 92 | 93 | MM.extend(MM.MercatorProjection, MM.Projection); 94 | -------------------------------------------------------------------------------- /examples/zoombox/zoombox.js: -------------------------------------------------------------------------------- 1 | com.modestmaps.ZoomBox = function(map) { 2 | 3 | this.map = map; 4 | 5 | var theBox = this; 6 | 7 | this.getMousePoint = function(e) { 8 | // start with just the mouse (x, y) 9 | var point = new com.modestmaps.Point(e.clientX, e.clientY); 10 | 11 | // correct for scrolled document 12 | point.x += document.body.scrollLeft + document.documentElement.scrollLeft; 13 | point.y += document.body.scrollTop + document.documentElement.scrollTop; 14 | 15 | // correct for nested offsets in DOM 16 | for(var node = this.map.parent; node; node = node.offsetParent) { 17 | point.x -= node.offsetLeft; 18 | point.y -= node.offsetTop; 19 | } 20 | 21 | return point; 22 | }; 23 | 24 | var boxDiv = document.createElement('div'); 25 | boxDiv.id = map.parent.id+'-zoombox'; 26 | boxDiv.style.cssText = 'margin:0; padding:0; position:absolute; top:0; left:0;' 27 | boxDiv.style.width = map.dimensions.x+'px'; 28 | boxDiv.style.height = map.dimensions.y+'px'; 29 | map.parent.appendChild(boxDiv); 30 | 31 | var box = document.createElement('div'); 32 | box.id = map.parent.id+'-zoombox-box'; 33 | box.style.cssText = 'margin:0; padding:0; border:1px dashed #888; background: rgba(255,255,255,0.25); position: absolute; top: 0; left: 0; width: 0; height: 0; display: none;'; 34 | boxDiv.appendChild(box); 35 | 36 | // TODO: respond to resize 37 | 38 | var mouseDownPoint = null; 39 | 40 | this.mouseDown = function(e) { 41 | if (e.shiftKey) { 42 | mouseDownPoint = theBox.getMousePoint(e); 43 | 44 | box.style.left = mouseDownPoint.x + 'px'; 45 | box.style.top = mouseDownPoint.y + 'px'; 46 | 47 | com.modestmaps.addEvent(map.parent, 'mousemove', theBox.mouseMove); 48 | com.modestmaps.addEvent(map.parent, 'mouseup', theBox.mouseUp); 49 | 50 | map.parent.style.cursor = 'crosshair'; 51 | 52 | return com.modestmaps.cancelEvent(e); 53 | } 54 | }; 55 | 56 | this.mouseMove = function(e) { 57 | var point = theBox.getMousePoint(e); 58 | box.style.display = 'block'; 59 | if (point.x < mouseDownPoint.x) { 60 | box.style.left = point.x + 'px'; 61 | } 62 | else { 63 | box.style.left = mouseDownPoint.x + 'px'; 64 | } 65 | box.style.width = Math.abs(point.x - mouseDownPoint.x) + 'px'; 66 | if (point.y < mouseDownPoint.y) { 67 | box.style.top = point.y + 'px'; 68 | } 69 | else { 70 | box.style.top = mouseDownPoint.y + 'px'; 71 | } 72 | box.style.height = Math.abs(point.y - mouseDownPoint.y) + 'px'; 73 | return com.modestmaps.cancelEvent(e); 74 | }; 75 | 76 | this.mouseUp = function(e) { 77 | 78 | var point = theBox.getMousePoint(e); 79 | 80 | var l1 = map.pointLocation(point); 81 | var l2 = map.pointLocation(mouseDownPoint); 82 | map.setExtent([l1,l2]); 83 | 84 | box.style.display = 'none'; 85 | com.modestmaps.removeEvent(map.parent, 'mousemove', theBox.mouseMove); 86 | com.modestmaps.removeEvent(map.parent, 'mouseup', theBox.mouseUp); 87 | 88 | map.parent.style.cursor = 'auto'; 89 | 90 | return com.modestmaps.cancelEvent(e); 91 | }; 92 | 93 | com.modestmaps.addEvent(boxDiv, 'mousedown', this.mouseDown); 94 | } 95 | -------------------------------------------------------------------------------- /examples/hurricanes/hurricanes-raph.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Modest Maps JS 5 | 6 | 7 | 8 | 9 | 10 | 11 | 81 | 82 | 83 |

Modest Maps JS

84 |

zoom in | zoom out 85 |
pan left | pan right | pan down | pan up

86 |
87 | 88 | 89 | -------------------------------------------------------------------------------- /src/coordinate.js: -------------------------------------------------------------------------------- 1 | // Coordinate 2 | // ---------- 3 | // An object representing a tile position, at as specified zoom level. 4 | // This is not necessarily a precise tile - `row`, `column`, and 5 | // `zoom` can be floating-point numbers, and the `container()` function 6 | // can be used to find the actual tile that contains the point. 7 | MM.Coordinate = function(row, column, zoom) { 8 | this.row = row; 9 | this.column = column; 10 | this.zoom = zoom; 11 | }; 12 | 13 | MM.Coordinate.prototype = { 14 | 15 | row: 0, 16 | column: 0, 17 | zoom: 0, 18 | 19 | toString: function() { 20 | return "(" + this.row.toFixed(3) + 21 | ", " + this.column.toFixed(3) + 22 | " @" + this.zoom.toFixed(3) + ")"; 23 | }, 24 | // Quickly generate a string representation of this coordinate to 25 | // index it in hashes. 26 | toKey: function() { 27 | // We've tried to use efficient hash functions here before but we took 28 | // them out. Contributions welcome but watch out for collisions when the 29 | // row or column are negative and check thoroughly (exhaustively) before 30 | // committing. 31 | return this.zoom + ',' + this.row + ',' + this.column; 32 | }, 33 | // Clone this object. 34 | copy: function() { 35 | return new MM.Coordinate(this.row, this.column, this.zoom); 36 | }, 37 | // Get the actual, rounded-number tile that contains this point. 38 | container: function() { 39 | // using floor here (not parseInt, ~~) because we want -0.56 --> -1 40 | return new MM.Coordinate(Math.floor(this.row), 41 | Math.floor(this.column), 42 | Math.floor(this.zoom)); 43 | }, 44 | // Recalculate this Coordinate at a different zoom level and return the 45 | // new object. 46 | zoomTo: function(destination) { 47 | var power = Math.pow(2, destination - this.zoom); 48 | return new MM.Coordinate(this.row * power, 49 | this.column * power, 50 | destination); 51 | }, 52 | // Recalculate this Coordinate at a different relative zoom level and return the 53 | // new object. 54 | zoomBy: function(distance) { 55 | var power = Math.pow(2, distance); 56 | return new MM.Coordinate(this.row * power, 57 | this.column * power, 58 | this.zoom + distance); 59 | }, 60 | // Move this coordinate up by `dist` coordinates 61 | up: function(dist) { 62 | if (dist === undefined) dist = 1; 63 | return new MM.Coordinate(this.row - dist, this.column, this.zoom); 64 | }, 65 | // Move this coordinate right by `dist` coordinates 66 | right: function(dist) { 67 | if (dist === undefined) dist = 1; 68 | return new MM.Coordinate(this.row, this.column + dist, this.zoom); 69 | }, 70 | // Move this coordinate down by `dist` coordinates 71 | down: function(dist) { 72 | if (dist === undefined) dist = 1; 73 | return new MM.Coordinate(this.row + dist, this.column, this.zoom); 74 | }, 75 | // Move this coordinate left by `dist` coordinates 76 | left: function(dist) { 77 | if (dist === undefined) dist = 1; 78 | return new MM.Coordinate(this.row, this.column - dist, this.zoom); 79 | } 80 | }; 81 | -------------------------------------------------------------------------------- /src/location.js: -------------------------------------------------------------------------------- 1 | // Location 2 | // -------- 3 | MM.Location = function(lat, lon) { 4 | this.lat = parseFloat(lat); 5 | this.lon = parseFloat(lon); 6 | }; 7 | 8 | MM.Location.prototype = { 9 | lat: 0, 10 | lon: 0, 11 | toString: function() { 12 | return "(" + this.lat.toFixed(3) + ", " + this.lon.toFixed(3) + ")"; 13 | }, 14 | copy: function() { 15 | return new MM.Location(this.lat, this.lon); 16 | } 17 | }; 18 | 19 | // returns approximate distance between start and end locations 20 | // 21 | // default unit is meters 22 | // 23 | // you can specify different units by optionally providing the 24 | // earth's radius in the units you desire 25 | // 26 | // Default is 6,378,000 metres, suggested values are: 27 | // 28 | // * 3963.1 statute miles 29 | // * 3443.9 nautical miles 30 | // * 6378 km 31 | // 32 | // see [Formula and code for calculating distance based on two lat/lon locations](http://jan.ucc.nau.edu/~cvm/latlon_formula.html) 33 | MM.Location.distance = function(l1, l2, r) { 34 | if (!r) { 35 | // default to meters 36 | r = 6378000; 37 | } 38 | var deg2rad = Math.PI / 180.0, 39 | a1 = l1.lat * deg2rad, 40 | b1 = l1.lon * deg2rad, 41 | a2 = l2.lat * deg2rad, 42 | b2 = l2.lon * deg2rad, 43 | c = Math.cos(a1) * Math.cos(b1) * Math.cos(a2) * Math.cos(b2), 44 | d = Math.cos(a1) * Math.sin(b1) * Math.cos(a2) * Math.sin(b2), 45 | e = Math.sin(a1) * Math.sin(a2); 46 | return Math.acos(c + d + e) * r; 47 | }; 48 | 49 | // Interpolates along a great circle, f between 0 and 1 50 | // 51 | // * FIXME: could be heavily optimized (lots of trig calls to cache) 52 | // * FIXME: could be inmproved for calculating a full path 53 | MM.Location.interpolate = function(l1, l2, f) { 54 | if (l1.lat === l2.lat && l1.lon === l2.lon) { 55 | return new MM.Location(l1.lat, l1.lon); 56 | } 57 | var deg2rad = Math.PI / 180.0, 58 | lat1 = l1.lat * deg2rad, 59 | lon1 = l1.lon * deg2rad, 60 | lat2 = l2.lat * deg2rad, 61 | lon2 = l2.lon * deg2rad; 62 | 63 | var d = 2 * Math.asin( 64 | Math.sqrt( 65 | Math.pow(Math.sin((lat1 - lat2) / 2), 2) + 66 | Math.cos(lat1) * Math.cos(lat2) * 67 | Math.pow(Math.sin((lon1 - lon2) / 2), 2))); 68 | 69 | var A = Math.sin((1-f)*d)/Math.sin(d); 70 | var B = Math.sin(f*d)/Math.sin(d); 71 | var x = A * Math.cos(lat1) * Math.cos(lon1) + 72 | B * Math.cos(lat2) * Math.cos(lon2); 73 | var y = A * Math.cos(lat1) * Math.sin(lon1) + 74 | B * Math.cos(lat2) * Math.sin(lon2); 75 | var z = A * Math.sin(lat1) + B * Math.sin(lat2); 76 | 77 | var latN = Math.atan2(z, Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2))); 78 | var lonN = Math.atan2(y,x); 79 | 80 | return new MM.Location(latN / deg2rad, lonN / deg2rad); 81 | }; 82 | 83 | // Returns bearing from one point to another 84 | // 85 | // * FIXME: bearing is not constant along significant great circle arcs. 86 | MM.Location.bearing = function(l1, l2) { 87 | var result = Math.atan2( 88 | Math.sin(lon1 - lon2) * 89 | Math.cos(lat2), 90 | Math.cos(lat1) * 91 | Math.sin(lat2) - 92 | Math.sin(lat1) * 93 | Math.cos(lat2) * 94 | Math.cos(lon1 - lon2) 95 | ) / -(Math.PI / 180); 96 | 97 | // map it into 0-360 range 98 | return (result < 0) ? result + 360 : result; 99 | }; -------------------------------------------------------------------------------- /examples/sprite-tiles/modestmaps.sprite-tiles.js: -------------------------------------------------------------------------------- 1 | (function(MM) { 2 | 3 | /** 4 | * The SpriteMapProvider creates
tiles with a background-image CSS 5 | * property of the tile URL, and scrolls the background image using a 6 | * provided offset, via setOffset(). The offsets are multiples of the 7 | * tile size, so use 1 to get a 256px offset instead of 256. Usage: 8 | * 9 | * var provider = new SpriteMapProvider(new mm.TemplatedMapProvider(...)); 10 | * provider.setOffset(1); // offsets vertically by 256px 11 | * provider.setOffset(2); // by 512px 12 | */ 13 | MM.SpriteMapProvider = function(template_provider) { 14 | this.template_provider = template_provider; 15 | this.offset = 0; 16 | this.tiles = {}; 17 | // FIXME: this is hard-coded now, but it should come from the map or the provider. 18 | // Currently, though, there's no way for the provider to know about the map. 19 | this.tileSize = new MM.Point(256, 256); 20 | }; 21 | 22 | MM.SpriteMapProvider.prototype = { 23 | tiles: null, 24 | tileSize: null, 25 | offset: 0, 26 | 27 | getOffset: function() { 28 | return this.offset; 29 | }, 30 | setOffset: function(offset) { 31 | if (this.offset != offset) { 32 | this.offset = offset; 33 | this.applyOffsets(); 34 | } 35 | }, 36 | 37 | applyOffset: function(tile) { 38 | var left = 0, 39 | top = -this.offset * this.tileSize.y; 40 | tile.style.backgroundPosition = left + "px " + top + "px"; 41 | }, 42 | 43 | applyOffsets: function() { 44 | for (var key in this.tiles) { 45 | this.applyOffset(this.tiles[key]); 46 | } 47 | }, 48 | 49 | getTile: function(coord) { 50 | var key = coord.toKey(); 51 | if (this.tiles.hasOwnProperty(key)) { 52 | return this.tiles[key]; 53 | } else { 54 | var url = this.template_provider.getTileUrl(coord); 55 | if (url) { 56 | var tile = document.createElement("div"); 57 | tile.style.backgroundRepeat = "no-repeat"; 58 | tile.backgroundTimeout = setTimeout(function() { 59 | tile.style.backgroundImage = "url(" + url + ")"; 60 | }, 100); 61 | 62 | /* 63 | * OTE: because using matrix transforms invalidates 64 | * explicit width and height values, we need to put a 65 | * "strut" inside each tile div that provides its intrinsic 66 | * size. This has the awesome side benefit of scaling 67 | * automatically. 68 | */ 69 | var strut = tile.appendChild(document.createElement("span")); 70 | strut.style.display = "block"; 71 | strut.style.width = this.tileSize.x + "px"; 72 | strut.style.height = this.tileSize.y + "px"; 73 | 74 | this.tiles[key] = tile; 75 | this.applyOffset(tile); 76 | return tile; 77 | } else { 78 | return null; 79 | } 80 | } 81 | }, 82 | 83 | releaseTile: function(coord) { 84 | var key = coord.toKey(), 85 | tile = this.tiles[key]; 86 | // clearTimeout(tile.backgroundTimeout); 87 | delete this.tiles[key]; 88 | }, 89 | 90 | reAddTile: function(key, coord, tile) { 91 | // console.log("re-add:", key, tile); 92 | this.tiles[key] = tile; 93 | this.applyOffset(tile); 94 | } 95 | }; 96 | 97 | })(com.modestmaps); 98 | -------------------------------------------------------------------------------- /examples/hello-oakland/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Modest Maps JS - Hello Oakland 5 | 6 | 77 | 89 | 90 | 91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 | 103 | 104 | -------------------------------------------------------------------------------- /examples/geojson/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ModestMaps JS: GeoJSON Markers 5 | 6 | 7 | 70 | 105 | 106 | 107 |

ModestMaps JS: GeoJSON Markers

108 |
109 |
110 | 111 | 112 | -------------------------------------------------------------------------------- /examples/node/modestmaps-static.js: -------------------------------------------------------------------------------- 1 | var MM = require('../../modestmaps.js'), 2 | Canvas = require('canvas'), 3 | Image = Canvas.Image; 4 | get = require('get'), 5 | express = require('express'), 6 | fs = require('fs'); 7 | 8 | function renderStaticMap(provider, dimensions, zoom, location, callback) { 9 | 10 | var canvas = new Canvas(dimensions.x, dimensions.y), 11 | ctx = canvas.getContext('2d'), 12 | // default to Google-y Mercator style maps 13 | projection = new MM.MercatorProjection(0, 14 | MM.deriveTransformation(-Math.PI, Math.PI, 0, 0, 15 | Math.PI, Math.PI, 1, 0, 16 | -Math.PI, -Math.PI, 0, 1)), 17 | tileSize = new MM.Point(256, 256); 18 | 19 | var centerCoordinate = projection.locationCoordinate(location).zoomTo(zoom); 20 | 21 | function pointCoordinate(point) { 22 | // new point coordinate reflecting distance from map center, in tile widths 23 | var coord = centerCoordinate.copy(); 24 | coord.column += (point.x - dimensions.x/2) / tileSize.x; 25 | coord.row += (point.y - dimensions.y/2) / tileSize.y; 26 | return coord; 27 | }; 28 | 29 | function coordinatePoint(coord) { 30 | // Return an x, y point on the map image for a given coordinate. 31 | if (coord.zoom != zoom) { 32 | coord = coord.zoomTo(zoom); 33 | } 34 | var point = new MM.Point(dimensions.x/2, dimensions.y/2); 35 | point.x += tileSize.x * (coord.column - centerCoordinate.column); 36 | point.y += tileSize.y * (coord.row - centerCoordinate.row); 37 | return point; 38 | } 39 | 40 | var startCoord = pointCoordinate(new MM.Point(0,0)).container(), 41 | endCoord = pointCoordinate(dimensions).container(); 42 | 43 | var numRequests = 0, 44 | completeRequests = 0; 45 | 46 | function checkDone() { 47 | if (completeRequests == numRequests) { 48 | callback(null, canvas); 49 | } 50 | } 51 | 52 | function getTile(url, p) { 53 | new get(url).asBuffer(function(error,data) { 54 | if (error) { 55 | callback(url + ' error: ' + error); 56 | } 57 | else { 58 | var img = new Image(); 59 | img.src = data; 60 | ctx.drawImage(img, p.x, p.y, tileSize.x, tileSize.y); 61 | completeRequests++; 62 | checkDone(); 63 | } 64 | }); 65 | } 66 | 67 | for (var column = startCoord.column; column <= endCoord.column; column++) { 68 | for (var row = startCoord.row; row <= endCoord.row; row++) { 69 | var c = new MM.Coordinate(row, column, zoom), 70 | url = provider.getTileUrl(c), 71 | p = coordinatePoint(c); 72 | if (url) { 73 | getTile(url, p); 74 | numRequests++; 75 | } 76 | } 77 | } 78 | 79 | } 80 | 81 | /* 82 | var provider = new MM.TemplatedMapProvider("http://tile.openstreetmap.org/{Z}/{X}/{Y}.png"); 83 | var dimensions = new MM.Point(800, 600); 84 | var zoom = 11; 85 | var location = new MM.Location(37.774929, -122.419415); 86 | 87 | renderStaticMap(provider, dimensions, 11, location, function(err, canvas) { 88 | if (err) { 89 | throw err; 90 | } 91 | var out = fs.createWriteStream(__dirname + '/map.png'), 92 | stream = canvas.createPNGStream(); 93 | stream.on('data', function(chunk){ 94 | out.write(chunk); 95 | }); 96 | stream.on('end', function(){ 97 | console.log('saved map.png'); 98 | }); 99 | }); 100 | */ 101 | 102 | // just one for now... 103 | var providers = { 104 | osm: new MM.TemplatedLayer("http://tile.openstreetmap.org/{Z}/{X}/{Y}.png") 105 | } 106 | 107 | var app = express.createServer(); 108 | 109 | app.get('/map', function(req,res) { 110 | var provider = providers[req.param("provider", "osm")], // default osm 111 | width = req.param("width", 800), 112 | height = req.param("height", 600), 113 | dimensions = new MM.Point(width, height), 114 | zoom = parseInt(req.param("zoom", 1)), 115 | lat = req.param("lat", 0.0), 116 | lon = req.param("lon", 0.0), 117 | location = new MM.Location(lat, lon); 118 | renderStaticMap(provider, dimensions, zoom, location, function(err,canvas) { 119 | if (err) { 120 | res.send(new Error(err)); 121 | } else { 122 | res.header('Content-Type', 'image/png'); 123 | res.send(canvas.toBuffer()); 124 | } 125 | }); 126 | }); 127 | 128 | app.listen(3000); 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /examples/markerclip/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Modest Maps JS Marker Demo 5 | 6 | 114 | 134 | 135 | 136 |

Modest Maps JS Marker Demo

137 |
138 |
139 | 140 | 141 | -------------------------------------------------------------------------------- /src/hash.js: -------------------------------------------------------------------------------- 1 | 2 | var HAS_HASHCHANGE = (function() { 3 | var doc_mode = window.documentMode; 4 | return ('onhashchange' in window) && 5 | (doc_mode === undefined || doc_mode > 7); 6 | })(); 7 | 8 | MM.Hash = function(map) { 9 | this.onMapMove = MM.bind(this.onMapMove, this); 10 | this.onHashChange = MM.bind(this.onHashChange, this); 11 | if (map) { 12 | this.init(map); 13 | } 14 | }; 15 | 16 | MM.Hash.prototype = { 17 | map: null, 18 | lastHash: null, 19 | 20 | parseHash: function(hash) { 21 | var args = hash.split("/"); 22 | if (args.length == 3) { 23 | var zoom = parseInt(args[0], 10), 24 | lat = parseFloat(args[1]), 25 | lon = parseFloat(args[2]); 26 | if (isNaN(zoom) || isNaN(lat) || isNaN(lon)) { 27 | return false; 28 | } else { 29 | return { 30 | center: new MM.Location(lat, lon), 31 | zoom: zoom 32 | }; 33 | } 34 | } else { 35 | return false; 36 | } 37 | }, 38 | 39 | formatHash: function(map) { 40 | var center = map.getCenter(), 41 | zoom = map.getZoom(), 42 | precision = Math.max(0, Math.ceil(Math.log(zoom) / Math.LN2)); 43 | return "#" + [zoom, 44 | center.lat.toFixed(precision), 45 | center.lon.toFixed(precision) 46 | ].join("/"); 47 | }, 48 | 49 | init: function(map) { 50 | this.map = map; 51 | this.map.addCallback("drawn", this.onMapMove); 52 | // reset the hash 53 | this.lastHash = null; 54 | this.onHashChange(); 55 | 56 | if (!this.isListening) { 57 | this.startListening(); 58 | } 59 | }, 60 | 61 | remove: function() { 62 | this.map = null; 63 | if (this.isListening) { 64 | this.stopListening(); 65 | } 66 | }, 67 | 68 | onMapMove: function(map) { 69 | // bail if we're moving the map (updating from a hash), 70 | // or if the map has no zoom set 71 | if (this.movingMap || this.map.zoom === 0) { 72 | return false; 73 | } 74 | var hash = this.formatHash(map); 75 | if (this.lastHash != hash) { 76 | location.replace(hash); 77 | this.lastHash = hash; 78 | } 79 | }, 80 | 81 | movingMap: false, 82 | update: function() { 83 | var hash = location.hash; 84 | if (hash === this.lastHash) { 85 | // console.info("(no change)"); 86 | return; 87 | } 88 | var sansHash = hash.substr(1), 89 | parsed = this.parseHash(sansHash); 90 | if (parsed) { 91 | // console.log("parsed:", parsed.zoom, parsed.center.toString()); 92 | this.movingMap = true; 93 | this.map.setCenterZoom(parsed.center, parsed.zoom); 94 | this.movingMap = false; 95 | } else { 96 | // console.warn("parse error; resetting:", this.map.getCenter(), this.map.getZoom()); 97 | this.onMapMove(this.map); 98 | } 99 | }, 100 | 101 | // defer hash change updates every 100ms 102 | changeDefer: 100, 103 | changeTimeout: null, 104 | onHashChange: function() { 105 | // throttle calls to update() so that they only happen every 106 | // `changeDefer` ms 107 | if (!this.changeTimeout) { 108 | var that = this; 109 | this.changeTimeout = setTimeout(function() { 110 | that.update(); 111 | that.changeTimeout = null; 112 | }, this.changeDefer); 113 | } 114 | }, 115 | 116 | isListening: false, 117 | hashChangeInterval: null, 118 | startListening: function() { 119 | if (HAS_HASHCHANGE) { 120 | window.addEventListener("hashchange", this.onHashChange, false); 121 | } else { 122 | clearInterval(this.hashChangeInterval); 123 | this.hashChangeInterval = setInterval(this.onHashChange, 50); 124 | } 125 | this.isListening = true; 126 | }, 127 | 128 | stopListening: function() { 129 | if (HAS_HASHCHANGE) { 130 | window.removeEventListener("hashchange", this.onHashChange); 131 | } else { 132 | clearInterval(this.hashChangeInterval); 133 | } 134 | this.isListening = false; 135 | } 136 | }; 137 | -------------------------------------------------------------------------------- /examples/polar/polar.js: -------------------------------------------------------------------------------- 1 | // THe following values were obtained http://nsidc.org/data/atlas/epsg_3411.html 2 | 3 | // North 4 | var la0=-45; // Central 5 | var phi1=90; // Data from http://nsidc.org/data/atlas/epsg_3411.html 6 | var phic=70; // optional 7 | var a=6378273.0; // Hughes ellipsoid required here http://nsidc.org/data/polar_stereo/ps_grids.html 8 | var e=0.081816153; 9 | var k0=1; // Scale factor 10 | 11 | function unproject(x, y) { 12 | var degRad=Math.PI / 180; 13 | var radDeg=180 / Math.PI; 14 | 15 | // Equation 14-15 16 | var m1=Math.cos(phi1 * degRad) / Math.pow((1 - Math.pow(e, 2) * Math.pow(Math.sin(phi1 * degRad), 2)), 1 / 2); 17 | 18 | // Equation 3-1 19 | var X1=2 * Math.atan(Math.tan((45 + phi1 / 2) * degRad) * Math.pow((1 - e * Math.sin(phi1 * degRad)) / (1 + e * Math.sin(phi1 * degRad)), e / 2)) - Math.PI / 2; 20 | 21 | // Equations 21-18 / 38 22 | var p=Math.pow(Math.pow(x, 2) + Math.pow(y, 2), 1 / 2); 23 | var ce=2 * Math.atan(p * Math.cos(X1) / (2 * a * k0 * m1)); 24 | 25 | //Equation 21-37 26 | var X=Math.asin(Math.cos(ce) * Math.sin(X1) + (y * Math.sin(ce) * Math.cos(X1) / p)); 27 | 28 | // Equation 3-4 29 | // Using X as the first trial for phi 30 | var phi=2 * Math.atan(Math.tan(Math.PI / 4 + X / 2) * Math.pow((1 + e * Math.sin(X)) / (1 - e * Math.sin(X)), e / 2)) - Math.PI / 2; 31 | // Using this new trial value for phi, not for X 32 | phi=2 * Math.atan(Math.tan(Math.PI / 4 + X / 2) * Math.pow((1 + e * Math.sin(phi)) / (1 - e * Math.sin(phi)), e / 2)) - Math.PI / 2; 33 | phi=(2 * Math.atan(Math.tan(Math.PI / 4 + X / 2) * Math.pow((1 + e * Math.sin(phi)) / (1 - e * Math.sin(phi)), e / 2)) - Math.PI / 2) * radDeg; 34 | 35 | // Equation 21-36 36 | // The next trial calculation produces the same phi to seven decimals. Therefore, this is phi. 37 | var la= la0 + Math.atan(x * Math.sin(ce) / (p * Math.cos(X1) * Math.cos(ce) - y * Math.sin(X1) * Math.sin(ce))) * radDeg; 38 | 39 | // If the denominator of the arctan argument is negative (reversed in Snyder), it is necessary to add or subtract 180, 40 | // whicever will place final la between +180 and -180. 41 | var arctanDenominator = (p * Math.cos(X1) * Math.cos(ce) - y * Math.sin(X1) * Math.sin(ce)); 42 | 43 | //Reverse coordinates 44 | if(arctanDenominator < 0) 45 | { 46 | if (la < 0) la += 180; 47 | else if(la > 0) la -= 180; 48 | } 49 | 50 | return { x: la, y: phi }; 51 | } 52 | 53 | // la = lon; phi = lat 54 | function project(phi, la) { 55 | 56 | var degRad=Math.PI / 180; 57 | var radDeg=180 / Math.PI 58 | 59 | // Equation 3-1 60 | var X1=2 * Math.atan(Math.tan((45 + phi1 / 2) * degRad) * Math.pow((1 - e * Math.sin(phi1 * degRad)) / (1 + e * Math.sin(phi1 * degRad)), e / 2)) - Math.PI / 2; 61 | var X=2 * Math.atan(Math.tan((45 + phi / 2) * degRad) * Math.pow((1 - e * Math.sin(phi * degRad)) / (1 + e * Math.sin(phi * degRad)), e / 2)) - Math.PI / 2; 62 | 63 | // Equation 14-15 64 | var m1=Math.cos(phi1 * degRad) / Math.pow((1 - Math.pow(e, 2) * Math.pow(Math.sin(phi1 * degRad), 2)), 1 / 2); 65 | var m=Math.cos(phi * degRad) / Math.pow((1 - Math.pow(e, 2) * Math.pow(Math.sin(phi * degRad), 2)), 1 / 2); 66 | 67 | if (phi1 == 90) { 68 | 69 | // Equation 15-9 70 | var t = Math.tan((45-phi/2)*degRad) / Math.pow( (1-e*Math.sin(phi*degRad))/(1+e*Math.sin(phi*degRad)), e/2); 71 | 72 | var p; 73 | if (phic) { 74 | var tc = Math.tan((45-phic/2)*degRad) / Math.pow( (1-e*Math.sin(phic*degRad))/(1+e*Math.sin(phic*degRad)), e/2); 75 | var mc = Math.cos(phic * degRad) / Math.pow((1 - Math.pow(e, 2) * Math.pow(Math.sin(phic * degRad), 2)), 1 / 2); 76 | p = a*mc*t/tc; 77 | } 78 | else { 79 | // Equation 21-33 80 | p = 2 * a * k0 * t / Math.pow( Math.pow(1+e,1+e)*Math.pow(1-e,1-e), 1/2 ); 81 | } 82 | 83 | // Equations 21-30 / 31 / 32 84 | var x = p * Math.sin((la - la0) * degRad); 85 | var y = -p * Math.cos((la - la0) * degRad); 86 | var k = p / (a * m); 87 | 88 | return { x: x, y: y }; 89 | } 90 | else { 91 | // Equation 21-27 92 | var A=2 * a * k0 * m1 / (Math.cos(X1) * (1 + Math.sin(X1) * Math.sin(X) + Math.cos(X1) * Math.cos(X) * Math.cos((la - la0) * degRad))); 93 | 94 | //console.log(A); // expecting 64501707.7 95 | 96 | // Equations 21-24 / 25 / 26 97 | var x=A * Math.cos(X) * Math.sin((la - la0) * degRad); 98 | var y=A * (Math.cos(X1) * Math.sin(X) - Math.sin(X1) * Math.cos(X) * Math.cos((la - la0) * degRad)); 99 | var k=A * Math.cos(X) / (a * m); 100 | 101 | //console.log('x', x); // expecting 971630.8 102 | //console.log('y', y); // -1063049.3 103 | //console.log('k', k); 104 | 105 | return { x: x, y: y }; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /examples/spotlight/spotlight.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A Spotlight object instance draws "punched out" circles on a canvas. The 3 | * first argument should be an HTML Canvas instance. The second is an optional 4 | * canvas fillStyle (presumably, any CSS color string, e.g. "rgba(0,0,0,.5)" 5 | * for 50% transparent black). 6 | */ 7 | var Spotlight = function(canvas, fillStyle) { 8 | this.canvas = canvas; 9 | this.ctx = canvas.getContext("2d"); 10 | this.clear(); 11 | if (fillStyle) { 12 | this.fillStyle = fillStyle; 13 | } 14 | }; 15 | 16 | Spotlight.prototype = { 17 | fillStyle: "rgba(0,0,0,.6)", 18 | radius: 40, 19 | 20 | // clearing resets the canvas and fills it with the fillStyle 21 | clear: function() { 22 | this.canvas.width = this.canvas.width; 23 | this.ctx.globalCompositeOperation = "source-over"; 24 | this.fill(); 25 | }, 26 | 27 | // fill the canvas with the color defined by fillStyle 28 | fill: function() { 29 | this.ctx.fillStyle = this.fillStyle; 30 | this.ctx.beginPath(); 31 | this.ctx.rect(0, 0, this.canvas.width, this.canvas.height); 32 | this.ctx.fill(); 33 | }, 34 | 35 | /** 36 | * Draw an array of points ({x, y}) as white circles. Each circle may 37 | * define its own radius, or we fall back on the value radius argument. 38 | */ 39 | drawPoints: function(points) { 40 | this.ctx.fillStyle = "white"; 41 | var TWO_PI = Math.PI * 2, 42 | radius = this.radius; 43 | /* 44 | * NOTE: we have to draw each circle as a distinct path because 45 | * otherwise their endpoints are connected as though each arc is a 46 | * subpath. 47 | */ 48 | for (var i = 0; i < points.length; i++) { 49 | var p = points[i], 50 | r = ("radius" in p) 51 | ? p.radius 52 | : radius; 53 | this.ctx.beginPath(); 54 | this.ctx.arc(p.x, p.y, r, 0, TWO_PI, true); 55 | this.ctx.closePath(); 56 | this.ctx.fill(); 57 | } 58 | }, 59 | 60 | /** 61 | * Draw an array of points ({x, y}) as circles "punched out" from the fill 62 | * color. This produces the "spotlight" effect. 63 | */ 64 | punchout: function(points) { 65 | var time = +new Date(); 66 | this.clear(); 67 | 68 | this.ctx.globalCompositeOperation = "destination-out"; 69 | this.drawPoints(points); 70 | console.log(points.length, "pts took", (new Date() - time), "ms"); 71 | } 72 | }; 73 | 74 | var SpotlightLayer = function(canvas, fillStyle) { 75 | this.parent = canvas || document.createElement("canvas"); 76 | this.parent.style.position = "absolute"; 77 | this.spotlight = new Spotlight(this.parent, fillStyle); 78 | this.locations = []; 79 | }; 80 | 81 | SpotlightLayer.prototype = { 82 | positioned: false, 83 | locations: null, 84 | 85 | addLocation: function(loc) { 86 | if (this.map) { 87 | loc.coord = this.map.locationCoordinate(loc); 88 | } 89 | this.locations.push(loc); 90 | this.draw(); 91 | }, 92 | 93 | removeLocation: function(loc) { 94 | var len = this.locations.length, 95 | removed = false; 96 | for (var i = 0; i < len; i++) { 97 | if (this.locations[i] === loc) { 98 | this.locations.splice(i, 1); 99 | removed = true; 100 | break; 101 | } 102 | } 103 | if (removed) { 104 | this.draw(); 105 | } 106 | }, 107 | 108 | addLocations: function(locs) { 109 | var len = locs.length; 110 | if (this.map) { 111 | for (var i = 0; i < len; i++) { 112 | locs[i].coord = this.map.locationCoordinate(locs[i]); 113 | } 114 | } 115 | for (var i = 0; i < len; i++) { 116 | this.locations.push(locs[i]); 117 | } 118 | this.draw(); 119 | }, 120 | 121 | removeAllLocations: function() { 122 | this.locations = []; 123 | this.draw(); 124 | }, 125 | 126 | draw: function() { 127 | var map = this.map, 128 | canvas = this.parent; 129 | 130 | if (canvas.parentNode != map.parent) { 131 | map.parent.appendChild(canvas); 132 | } 133 | 134 | canvas.width = map.dimensions.x; 135 | canvas.height = map.dimensions.y; 136 | 137 | if (this.locations && this.locations.length) { 138 | var points = this.locations.map(function(loc) { 139 | var coord = loc.coord || (loc.coord = map.locationCoordinate(loc)), 140 | point = map.coordinatePoint(coord); 141 | if ("radius" in loc) point.radius = loc.radius; 142 | return point; 143 | }); 144 | this.spotlight.punchout(points); 145 | } else { 146 | this.spotlight.clear(); 147 | } 148 | } 149 | }; 150 | 151 | 152 | -------------------------------------------------------------------------------- /src/extent.js: -------------------------------------------------------------------------------- 1 | 2 | // Extent 3 | // ---------- 4 | // An object representing a map's rectangular extent, defined by its north, 5 | // south, east and west bounds. 6 | 7 | MM.Extent = function(north, west, south, east) { 8 | if (north instanceof MM.Location && 9 | west instanceof MM.Location) { 10 | var northwest = north, 11 | southeast = west; 12 | 13 | north = northwest.lat; 14 | west = northwest.lon; 15 | south = southeast.lat; 16 | east = southeast.lon; 17 | } 18 | if (isNaN(south)) south = north; 19 | if (isNaN(east)) east = west; 20 | this.north = Math.max(north, south); 21 | this.south = Math.min(north, south); 22 | this.east = Math.max(east, west); 23 | this.west = Math.min(east, west); 24 | }; 25 | 26 | MM.Extent.prototype = { 27 | // boundary attributes 28 | north: 0, 29 | south: 0, 30 | east: 0, 31 | west: 0, 32 | 33 | copy: function() { 34 | return new MM.Extent(this.north, this.west, this.south, this.east); 35 | }, 36 | 37 | toString: function(precision) { 38 | if (isNaN(precision)) precision = 3; 39 | return [ 40 | this.north.toFixed(precision), 41 | this.west.toFixed(precision), 42 | this.south.toFixed(precision), 43 | this.east.toFixed(precision) 44 | ].join(", "); 45 | }, 46 | 47 | // getters for the corner locations 48 | northWest: function() { 49 | return new MM.Location(this.north, this.west); 50 | }, 51 | southEast: function() { 52 | return new MM.Location(this.south, this.east); 53 | }, 54 | northEast: function() { 55 | return new MM.Location(this.north, this.east); 56 | }, 57 | southWest: function() { 58 | return new MM.Location(this.south, this.west); 59 | }, 60 | // getter for the center location 61 | center: function() { 62 | return new MM.Location( 63 | this.south + (this.north - this.south) / 2, 64 | this.east + (this.west - this.east) / 2 65 | ); 66 | }, 67 | 68 | // extend the bounds to include a location's latitude and longitude 69 | encloseLocation: function(loc) { 70 | if (loc.lat > this.north) this.north = loc.lat; 71 | if (loc.lat < this.south) this.south = loc.lat; 72 | if (loc.lon > this.east) this.east = loc.lon; 73 | if (loc.lon < this.west) this.west = loc.lon; 74 | }, 75 | 76 | // extend the bounds to include multiple locations 77 | encloseLocations: function(locations) { 78 | var len = locations.length; 79 | for (var i = 0; i < len; i++) { 80 | this.encloseLocation(locations[i]); 81 | } 82 | }, 83 | 84 | // reset bounds from a list of locations 85 | setFromLocations: function(locations) { 86 | var len = locations.length, 87 | first = locations[0]; 88 | this.north = this.south = first.lat; 89 | this.east = this.west = first.lon; 90 | for (var i = 1; i < len; i++) { 91 | this.encloseLocation(locations[i]); 92 | } 93 | }, 94 | 95 | // extend the bounds to include another extent 96 | encloseExtent: function(extent) { 97 | if (extent.north > this.north) this.north = extent.north; 98 | if (extent.south < this.south) this.south = extent.south; 99 | if (extent.east > this.east) this.east = extent.east; 100 | if (extent.west < this.west) this.west = extent.west; 101 | }, 102 | 103 | // determine if a location is within this extent 104 | containsLocation: function(loc) { 105 | return loc.lat >= this.south && 106 | loc.lat <= this.north && 107 | loc.lon >= this.west && 108 | loc.lon <= this.east; 109 | }, 110 | 111 | // turn an extent into an array of locations containing its northwest 112 | // and southeast corners (used in MM.Map.setExtent()) 113 | toArray: function() { 114 | return [this.northWest(), this.southEast()]; 115 | } 116 | }; 117 | 118 | MM.Extent.fromString = function(str) { 119 | var parts = str.split(/\s*,\s*/); 120 | if (parts.length != 4) { 121 | throw "Invalid extent string (expecting 4 comma-separated numbers)"; 122 | } 123 | return new MM.Extent( 124 | parseFloat(parts[0]), 125 | parseFloat(parts[1]), 126 | parseFloat(parts[2]), 127 | parseFloat(parts[3]) 128 | ); 129 | }; 130 | 131 | MM.Extent.fromArray = function(locations) { 132 | var extent = new MM.Extent(); 133 | extent.setFromLocations(locations); 134 | return extent; 135 | }; 136 | 137 | -------------------------------------------------------------------------------- /tools/README: -------------------------------------------------------------------------------- 1 | ============================================================================== 2 | YUI Compressor 3 | ============================================================================== 4 | 5 | NAME 6 | 7 | YUI Compressor - The Yahoo! JavaScript and CSS Compressor 8 | 9 | SYNOPSIS 10 | 11 | Usage: java -jar yuicompressor-x.y.z.jar [options] [input file] 12 | 13 | Global Options 14 | -h, --help Displays this information 15 | --type Specifies the type of the input file 16 | --charset Read the input file using 17 | --line-break Insert a line break after the specified column number 18 | -v, --verbose Display informational messages and warnings 19 | -o Place the output into . Defaults to stdout. 20 | 21 | JavaScript Options 22 | --nomunge Minify only, do not obfuscate 23 | --preserve-semi Preserve all semicolons 24 | --disable-optimizations Disable all micro optimizations 25 | 26 | DESCRIPTION 27 | 28 | The YUI Compressor is a JavaScript compressor which, in addition to removing 29 | comments and white-spaces, obfuscates local variables using the smallest 30 | possible variable name. This obfuscation is safe, even when using constructs 31 | such as 'eval' or 'with' (although the compression is not optimal is those 32 | cases) Compared to jsmin, the average savings is around 20%. 33 | 34 | The YUI Compressor is also able to safely compress CSS files. The decision 35 | on which compressor is being used is made on the file extension (js or css) 36 | 37 | GLOBAL OPTIONS 38 | 39 | -h, --help 40 | Prints help on how to use the YUI Compressor 41 | 42 | --line-break 43 | Some source control tools don't like files containing lines longer than, 44 | say 8000 characters. The linebreak option is used in that case to split 45 | long lines after a specific column. It can also be used to make the code 46 | more readable, easier to debug (especially with the MS Script Debugger) 47 | Specify 0 to get a line break after each semi-colon in JavaScript, and 48 | after each rule in CSS. 49 | 50 | --type js|css 51 | The type of compressor (JavaScript or CSS) is chosen based on the 52 | extension of the input file name (.js or .css) This option is required 53 | if no input file has been specified. Otherwise, this option is only 54 | required if the input file extension is neither 'js' nor 'css'. 55 | 56 | --charset character-set 57 | If a supported character set is specified, the YUI Compressor will use it 58 | to read the input file. Otherwise, it will assume that the platform's 59 | default character set is being used. The output file is encoded using 60 | the same character set. 61 | 62 | -o outfile 63 | Place output in file outfile. If not specified, the YUI Compressor will 64 | default to the standard output, which you can redirect to a file. 65 | 66 | -v, --verbose 67 | Display informational messages and warnings. 68 | 69 | JAVASCRIPT ONLY OPTIONS 70 | 71 | --nomunge 72 | Minify only. Do not obfuscate local symbols. 73 | 74 | --preserve-semi 75 | Preserve unnecessary semicolons (such as right before a '}') This option 76 | is useful when compressed code has to be run through JSLint (which is the 77 | case of YUI for example) 78 | 79 | --disable-optimizations 80 | Disable all the built-in micro optimizations. 81 | 82 | NOTES 83 | 84 | + If no input file is specified, it defaults to stdin. 85 | 86 | + The YUI Compressor requires Java version >= 1.4. 87 | 88 | + It is possible to prevent a local variable, nested function or function 89 | argument from being obfuscated by using "hints". A hint is a string that 90 | is located at the very beginning of a function body like so: 91 | 92 | function fn (arg1, arg2, arg3) { 93 | "arg2:nomunge, localVar:nomunge, nestedFn:nomunge"; 94 | 95 | ... 96 | var localVar; 97 | ... 98 | 99 | function nestedFn () { 100 | .... 101 | } 102 | 103 | ... 104 | } 105 | 106 | The hint itself disappears from the compressed file. 107 | 108 | + C-style comments starting with /*! are preserved. This is useful with 109 | comments containing copyright/license information. For example: 110 | 111 | /*! 112 | * TERMS OF USE - EASING EQUATIONS 113 | * Open source under the BSD License. 114 | * Copyright 2001 Robert Penner All rights reserved. 115 | */ 116 | 117 | becomes: 118 | 119 | /* 120 | * TERMS OF USE - EASING EQUATIONS 121 | * Open source under the BSD License. 122 | * Copyright 2001 Robert Penner All rights reserved. 123 | */ 124 | 125 | AUTHOR 126 | 127 | The YUI Compressor was written and is maintained by: 128 | Julien Lecomte 129 | The CSS portion is a port of Isaac Schlueter's cssmin utility. 130 | 131 | COPYRIGHT 132 | 133 | Copyright (c) 2007-2009, Yahoo! Inc. All rights reserved. 134 | 135 | LICENSE 136 | 137 | All code specific to YUI Compressor is issued under a BSD license. 138 | YUI Compressor extends and implements code from Mozilla's Rhino project. 139 | Rhino is issued under the Mozilla Public License (MPL), and MPL applies 140 | to the Rhino source and binaries that are distributed with YUI Compressor. -------------------------------------------------------------------------------- /examples/hurricanes/polygonmarker-raphael.js: -------------------------------------------------------------------------------- 1 | // namespacing! 2 | if (!com) { 3 | var com = { }; 4 | if (!com.modestmaps) { 5 | com.modestmaps = { }; 6 | } 7 | } 8 | 9 | com.modestmaps.PolygonMarker = function(map, locations, fillStyle, fillAlpha, strokeStyle) 10 | { 11 | this.fillStyle = fillStyle; 12 | this.fillAlpha = fillAlpha; 13 | this.strokeStyle = strokeStyle; 14 | 15 | this.coords = []; 16 | 17 | // top left 18 | var maxLat = locations[0].lat; 19 | var minLon = locations[0].lon; 20 | 21 | // bottom right 22 | var minLat = locations[0].lat; 23 | var maxLon = locations[0].lon; 24 | 25 | for (var i = 0; i < locations.length; i++) { 26 | this.coords.push(map.locationCoordinate(locations[i])); 27 | minLat = Math.min(minLat, locations[i].lat); 28 | maxLat = Math.max(maxLat, locations[i].lat); 29 | minLon = Math.min(minLon, locations[i].lon); 30 | maxLon = Math.max(maxLon, locations[i].lon); 31 | } 32 | 33 | var topLeftLocation = new com.modestmaps.Location(maxLat, minLon); 34 | var bottomRightLocation = new com.modestmaps.Location(minLat, maxLon); 35 | 36 | // console.log(topLeftLocation); 37 | // console.log(bottomRightLocation); 38 | 39 | this.topLeftCoord = map.locationCoordinate(topLeftLocation); 40 | this.bottomRightCoord = map.locationCoordinate(bottomRightLocation); 41 | 42 | // console.log(this.topLeftCoord); 43 | // console.log(this.bottomRightCoord); 44 | 45 | 46 | // listen for events 47 | 48 | var follower = this; 49 | var callback = function(m, a) { return follower.draw(m); }; 50 | map.addCallback('panned', callback); 51 | map.addCallback('zoomed', callback); 52 | map.addCallback('centered', callback); 53 | map.addCallback('extentset', callback); 54 | 55 | // get your div on 56 | 57 | this.div = document.createElement('div'); 58 | this.div.style.position = 'absolute'; 59 | map.parent.appendChild(this.div); 60 | 61 | this.draw(map); 62 | } 63 | 64 | com.modestmaps.PolygonMarker.prototype = { 65 | 66 | div: null, 67 | canvas: null, 68 | 69 | coords: null, 70 | topLeftCoord: null, 71 | bottomRightCoord: null, 72 | 73 | drawZoom: null, 74 | 75 | fillStyle: null, 76 | fillAlpha: null, 77 | strokeStyle: null, 78 | 79 | draw: function(map) 80 | { 81 | try { 82 | var point = map.coordinatePoint(this.topLeftCoord); 83 | 84 | } catch(e) { 85 | // too soon? 86 | return; 87 | } 88 | 89 | /* if(point.x + this.dimensions.x + this.offset.x < 0) { 90 | // too far left 91 | this.div.style.display = 'none'; 92 | 93 | } else if(point.y + this.dimensions.y + this.offset.y < 0) { 94 | // too far up 95 | this.div.style.display = 'none'; 96 | 97 | } else if(point.x + this.offset.x > map.dimensions.x) { 98 | // too far right 99 | this.div.style.display = 'none'; 100 | 101 | } else if(point.y + this.offset.y > map.dimensions.y) { 102 | // too far down 103 | this.div.style.display = 'none'; 104 | 105 | } else { 106 | this.div.style.display = 'block'; 107 | this.div.style.left = point.x + this.offset.x + 'px'; 108 | this.div.style.top = point.y + this.offset.y + 'px'; 109 | } */ 110 | 111 | this.div.style.display = 'block'; 112 | this.div.style.left = point.x + 'px'; 113 | this.div.style.top = point.y + 'px'; 114 | 115 | if (this.drawZoom != map.getZoom()) { 116 | 117 | var topLeftPoint = map.coordinatePoint(this.topLeftCoord); 118 | var bottomRightPoint = map.coordinatePoint(this.bottomRightCoord); 119 | 120 | var canvasWidth = bottomRightPoint.x - topLeftPoint.x; 121 | var canvasHeight = bottomRightPoint.y - topLeftPoint.y; 122 | 123 | if (this.canvas) { 124 | this.canvas.remove(); 125 | // TODO: resize? 126 | } 127 | 128 | this.canvas = Raphael(this.div, canvasWidth, canvasHeight); 129 | 130 | var points = []; 131 | for (var i = 0; i < this.coords.length; i++) { 132 | var point = map.coordinatePoint(this.coords[i]); 133 | point.x -= topLeftPoint.x; 134 | point.y -= topLeftPoint.y; 135 | points.push(point); 136 | } 137 | 138 | var pathParams = {}; 139 | 140 | if (this.fillStyle) { 141 | pathParams['fill'] = this.fillStyle; 142 | pathParams['fill-opacity'] = this.fillAlpha; 143 | } 144 | if (this.strokeStyle) pathParams['stroke'] = this.strokeStyle; 145 | 146 | var path = this.canvas.path(pathParams); 147 | 148 | path.moveTo(points[0].x, points[0].y); 149 | for (var i = 1; i < points.length; i++) { 150 | path.lineTo(points[i].x, points[i].y); 151 | } 152 | path.andClose(); 153 | 154 | this.drawZoom = map.getZoom(); 155 | } 156 | 157 | }, 158 | 159 | clear: function(){ 160 | this.canvas.clear(); 161 | this.coords = []; 162 | } 163 | }; 164 | -------------------------------------------------------------------------------- /examples/hurricanes/polygonmarker-canvas.js: -------------------------------------------------------------------------------- 1 | // namespacing! 2 | if (!com) { 3 | var com = { }; 4 | if (!com.modestmaps) { 5 | com.modestmaps = { }; 6 | } 7 | } 8 | 9 | com.modestmaps.PolygonMarker = function(map, locations, fillStyle, strokeStyle) 10 | { 11 | this.fillStyle = fillStyle; 12 | this.strokeStyle = strokeStyle; 13 | 14 | this.coords = []; 15 | 16 | // top left 17 | var maxLat = locations[0].lat; 18 | var minLon = locations[0].lon; 19 | 20 | // bottom right 21 | var minLat = locations[0].lat; 22 | var maxLon = locations[0].lon; 23 | 24 | for (var i = 0; i < locations.length; i++) { 25 | this.coords.push(map.locationCoordinate(locations[i])); 26 | minLat = Math.min(minLat, locations[i].lat); 27 | maxLat = Math.max(maxLat, locations[i].lat); 28 | minLon = Math.min(minLon, locations[i].lon); 29 | maxLon = Math.max(maxLon, locations[i].lon); 30 | } 31 | 32 | var topLeftLocation = new com.modestmaps.Location(maxLat, minLon); 33 | var bottomRightLocation = new com.modestmaps.Location(minLat, maxLon); 34 | 35 | // console.log(topLeftLocation); 36 | // console.log(bottomRightLocation); 37 | 38 | this.topLeftCoord = map.locationCoordinate(topLeftLocation); 39 | this.bottomRightCoord = map.locationCoordinate(bottomRightLocation); 40 | 41 | // console.log(this.topLeftCoord); 42 | // console.log(this.bottomRightCoord); 43 | 44 | 45 | // listen for events 46 | 47 | var follower = this; 48 | var callback = function(m, a) { return follower.draw(m); }; 49 | map.addCallback('panned', callback); 50 | map.addCallback('zoomed', callback); 51 | map.addCallback('centered', callback); 52 | map.addCallback('extentset', callback); 53 | 54 | // get your div on 55 | 56 | this.div = document.createElement('div'); 57 | this.div.style.position = 'absolute'; 58 | 59 | this.canvas = document.createElement('canvas'); 60 | this.div.appendChild(this.canvas); 61 | 62 | // excanvas help 63 | if (typeof G_vmlCanvasManager !== 'undefined') this.canvas = G_vmlCanvasManager.initElement(this.canvas); 64 | 65 | this.canvas.style.position = 'absolute'; 66 | this.canvas.style.left = '0px'; 67 | this.canvas.style.top = '0px'; 68 | 69 | // calculate points 70 | 71 | map.parent.appendChild(this.div); 72 | 73 | this.draw(map); 74 | } 75 | 76 | com.modestmaps.PolygonMarker.prototype = { 77 | 78 | div: null, 79 | canvas: null, 80 | 81 | coords: null, 82 | topLeftCoord: null, 83 | bottomRightCoord: null, 84 | 85 | drawZoom: null, 86 | 87 | fillStyle: null, 88 | strokeStyle: null, 89 | 90 | draw: function(map) 91 | { 92 | try { 93 | var point = map.coordinatePoint(this.topLeftCoord); 94 | 95 | } catch(e) { 96 | // too soon? 97 | return; 98 | } 99 | 100 | /* if(point.x + this.dimensions.x + this.offset.x < 0) { 101 | // too far left 102 | this.div.style.display = 'none'; 103 | 104 | } else if(point.y + this.dimensions.y + this.offset.y < 0) { 105 | // too far up 106 | this.div.style.display = 'none'; 107 | 108 | } else if(point.x + this.offset.x > map.dimensions.x) { 109 | // too far right 110 | this.div.style.display = 'none'; 111 | 112 | } else if(point.y + this.offset.y > map.dimensions.y) { 113 | // too far down 114 | this.div.style.display = 'none'; 115 | 116 | } else { 117 | this.div.style.display = 'block'; 118 | this.div.style.left = point.x + this.offset.x + 'px'; 119 | this.div.style.top = point.y + this.offset.y + 'px'; 120 | } */ 121 | 122 | this.div.style.display = 'block'; 123 | this.div.style.left = point.x + 'px'; 124 | this.div.style.top = point.y + 'px'; 125 | 126 | if (this.drawZoom != map.getZoom()) { 127 | 128 | var topLeftPoint = map.coordinatePoint(this.topLeftCoord); 129 | var bottomRightPoint = map.coordinatePoint(this.bottomRightCoord); 130 | 131 | this.canvas.width = bottomRightPoint.x - topLeftPoint.x; 132 | this.canvas.height = bottomRightPoint.y - topLeftPoint.y; 133 | 134 | var points = []; 135 | for (var i = 0; i < this.coords.length; i++) { 136 | var point = map.coordinatePoint(this.coords[i]); 137 | point.x -= topLeftPoint.x; 138 | point.y -= topLeftPoint.y; 139 | points.push(point); 140 | } 141 | 142 | var ctx = this.canvas.getContext('2d'); 143 | ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); 144 | if (this.strokeStyle) ctx.strokeStyle = this.strokeStyle; 145 | if (this.fillStyle) ctx.fillStyle = this.fillStyle; 146 | ctx.beginPath(); 147 | ctx.moveTo(points[0].x, points[0].y); 148 | for (var i = 1; i < points.length; i++) { 149 | ctx.lineTo(points[i].x, points[i].y); 150 | } 151 | // close path 152 | ctx.lineTo(points[0].x, points[0].y); 153 | if (this.fillStyle) ctx.fill(); 154 | if (this.strokeStyle) ctx.stroke(); 155 | 156 | this.drawZoom = map.getZoom(); 157 | } 158 | 159 | } 160 | 161 | }; 162 | -------------------------------------------------------------------------------- /examples/bubble/follower-canvas.js: -------------------------------------------------------------------------------- 1 | // namespacing! 2 | if (!com) { 3 | var com = { }; 4 | if (!com.modestmaps) { 5 | com.modestmaps = { }; 6 | } 7 | } 8 | 9 | (function(MM) { 10 | 11 | MM.Follower = function(map, location, content) 12 | { 13 | this.coord = map.locationCoordinate(location); 14 | 15 | this.offset = new MM.Point(0, 0); 16 | this.dimensions = new MM.Point(150, 150); 17 | this.margin = new MM.Point(10, 10); 18 | this.offset = new MM.Point(0, -this.dimensions.y); 19 | 20 | var follower = this; 21 | 22 | var callback = function(m, a) { return follower.draw(m); }; 23 | map.addCallback('drawn', callback); 24 | 25 | this.div = document.createElement('div'); 26 | this.div.style.position = 'absolute'; 27 | this.div.style.width = this.dimensions.x + 'px'; 28 | this.div.style.height = this.dimensions.y + 'px'; 29 | 30 | //this.div.style.backgroundColor = 'white'; 31 | //this.div.style.border = 'solid black 1px'; 32 | 33 | var shadow = document.createElement('canvas'); 34 | this.div.appendChild(shadow); 35 | if (typeof G_vmlCanvasManager !== 'undefined') shadow = G_vmlCanvasManager.initElement(shadow); 36 | shadow.style.position = 'absolute'; 37 | shadow.style.left = '0px'; 38 | shadow.style.top = '0px'; 39 | shadow.width = this.dimensions.x*2; 40 | shadow.height = this.dimensions.y; 41 | var ctx = shadow.getContext("2d"); 42 | ctx.transform(1, 0, -0.5, 0.5, 75, this.dimensions.y/2); 43 | ctx.fillStyle = "rgba(0,0,0,0.5)"; 44 | this.drawBubblePath(ctx); 45 | ctx.fill(); 46 | 47 | var bubble = document.createElement('canvas'); 48 | this.div.appendChild(bubble); 49 | if (typeof G_vmlCanvasManager !== 'undefined') bubble = G_vmlCanvasManager.initElement(bubble); 50 | bubble.style.position = 'absolute'; 51 | bubble.style.left = '0px'; 52 | bubble.style.top = '0px'; 53 | bubble.width = this.dimensions.x; 54 | bubble.height = this.dimensions.y; 55 | var bubCtx = bubble.getContext('2d'); 56 | bubCtx.strokeStyle = 'black'; 57 | bubCtx.fillStyle = 'white'; 58 | this.drawBubblePath(bubCtx); 59 | bubCtx.fill(); 60 | bubCtx.stroke(); 61 | 62 | var contentDiv = document.createElement('div'); 63 | contentDiv.style.position = 'absolute'; 64 | contentDiv.style.left = '0px'; 65 | contentDiv.style.top = '0px'; 66 | contentDiv.style.overflow = 'hidden'; 67 | contentDiv.style.width = (this.dimensions.x - this.margin.x) + 'px'; 68 | contentDiv.style.height = (this.dimensions.y - this.margin.y - 25) + 'px'; 69 | contentDiv.style.padding = this.margin.y + 'px ' + this.margin.x + 'px ' + this.margin.y + 'px ' + this.margin.x + 'px'; 70 | contentDiv.innerHTML = content; 71 | this.div.appendChild(contentDiv); 72 | 73 | MM.addEvent(contentDiv, 'mousedown', function(e) { 74 | if(!e) e = window.event; 75 | return MM.cancelEvent(e); 76 | }); 77 | 78 | map.parent.appendChild(this.div); 79 | 80 | this.draw(map); 81 | } 82 | 83 | MM.Follower.prototype = { 84 | 85 | div: null, 86 | coord: null, 87 | 88 | offset: null, 89 | dimensions: null, 90 | margin: null, 91 | 92 | draw: function(map) { 93 | try { 94 | var point = map.coordinatePoint(this.coord); 95 | } catch(e) { 96 | console.error(e); 97 | // too soon? 98 | return; 99 | } 100 | 101 | if(point.x + this.dimensions.x + this.offset.x < 0) { 102 | // too far left 103 | this.div.style.display = 'none'; 104 | 105 | } else if(point.y + this.dimensions.y + this.offset.y < 0) { 106 | // too far up 107 | this.div.style.display = 'none'; 108 | 109 | } else if(point.x + this.offset.x > map.dimensions.x) { 110 | // too far right 111 | this.div.style.display = 'none'; 112 | 113 | } else if(point.y + this.offset.y > map.dimensions.y) { 114 | // too far down 115 | this.div.style.display = 'none'; 116 | 117 | } else { 118 | this.div.style.display = 'block'; 119 | MM.moveElement(this.div, { 120 | x: Math.round(point.x + this.offset.x), 121 | y: Math.round(point.y + this.offset.y), 122 | scale: 1, 123 | width: this.dimensions.x, 124 | height: this.dimensions.y 125 | }); 126 | } 127 | }, 128 | 129 | drawBubblePath: function(ctx) { 130 | ctx.beginPath(); 131 | ctx.moveTo(10, this.dimensions.y); 132 | ctx.lineTo(35, this.dimensions.y-25); 133 | ctx.lineTo(this.dimensions.x-10, this.dimensions.y-25); 134 | ctx.quadraticCurveTo(this.dimensions.x, this.dimensions.y-25, this.dimensions.x, this.dimensions.y-35); 135 | ctx.lineTo(this.dimensions.x, 10); 136 | ctx.quadraticCurveTo(this.dimensions.x, 0, this.dimensions.x-10, 0); 137 | ctx.lineTo(10, 0); 138 | ctx.quadraticCurveTo(0, 0, 0, 10); 139 | ctx.lineTo(0, this.dimensions.y-35); 140 | ctx.quadraticCurveTo(0, this.dimensions.y-25, 10, this.dimensions.y-25); 141 | ctx.lineTo(15, this.dimensions.y-25); 142 | ctx.moveTo(10, this.dimensions.y); 143 | } 144 | 145 | }; 146 | 147 | })(com.modestmaps) 148 | -------------------------------------------------------------------------------- /test/browser/spec/Map.js: -------------------------------------------------------------------------------- 1 | describe('Map', function() { 2 | var map, div, sink; 3 | 4 | function Receiver() { } 5 | Receiver.prototype.receive = function() { }; 6 | 7 | beforeEach(function() { 8 | sink = new Receiver(); 9 | div = document.createElement('div'); 10 | div.id = +new Date(); 11 | div.style.width = 500; 12 | div.style.height = 500; 13 | 14 | var template = 'http://{S}tile.openstreetmap.org/{Z}/{X}/{Y}.png'; 15 | var subdomains = [ '', 'a.', 'b.', 'c.' ]; 16 | var provider = new MM.TemplatedLayer(template, subdomains); 17 | 18 | map = new MM.Map(div, provider, new MM.Point(400, 400)); 19 | map.setCenterZoom(new MM.Location(0, 0), 0); 20 | }); 21 | 22 | it('attaches itself to a parent div', function() { 23 | expect(map.parent).toEqual(div); 24 | }); 25 | 26 | describe('zoom restrictions and ranges', function() { 27 | 28 | it('has set a proper zoom level', function() { 29 | expect(map.getZoom()).toEqual(0); 30 | }); 31 | 32 | it('can restrict its zoomlevel', function() { 33 | map.setZoomRange(5, 6); 34 | map.setZoom(7); 35 | expect(map.getZoom()).toEqual(6); 36 | }); 37 | 38 | }); 39 | 40 | it('has a center coordinate', function() { 41 | expect(typeof map.coordinate.row).toEqual('number'); 42 | expect(typeof map.coordinate.column).toEqual('number'); 43 | expect(typeof map.coordinate.zoom).toEqual('number'); 44 | }); 45 | 46 | describe('Navigation', function() { 47 | it('binds and calls drawn', function() { 48 | spyOn(sink, 'receive'); 49 | map.addCallback('drawn', sink.receive); 50 | 51 | runs(function() { 52 | map.draw(); 53 | }); 54 | 55 | waits(500); 56 | 57 | runs(function() { 58 | expect(sink.receive).toHaveBeenCalledWith(map, undefined); 59 | }); 60 | }); 61 | 62 | it('binds and calls zoomed', function() { 63 | spyOn(sink, 'receive'); 64 | map.addCallback('zoomed', sink.receive); 65 | 66 | runs(function() { 67 | map.zoomIn(); 68 | }); 69 | 70 | waits(500); 71 | 72 | runs(function() { 73 | expect(sink.receive).toHaveBeenCalledWith(map, 1); 74 | }); 75 | }); 76 | 77 | it('binds and calls panned', function() { 78 | spyOn(sink, 'receive'); 79 | map.addCallback('panned', sink.receive); 80 | 81 | runs(function() { 82 | map.panBy(2, 2); 83 | }); 84 | 85 | waits(500); 86 | 87 | runs(function() { 88 | expect(sink.receive).toHaveBeenCalledWith(map, [2, 2]); 89 | }); 90 | }); 91 | }); 92 | 93 | describe('Layer Interface', function() { 94 | it('Can set a new layer at 0', function() { 95 | var p = new MM.TemplatedMapProvider( 96 | 'http://{S}.tile.openstreetmap.org/{Z}/{X}/{Y}.png', ['a']); 97 | var l = new MM.Layer(p); 98 | map.setLayerAt(0, l); 99 | 100 | expect(map.getLayerAt(0)).toEqual(l); 101 | }); 102 | it('Can insert a new layer at 0', function() { 103 | var p = new MM.TemplatedMapProvider( 104 | 'http://{S}.tile.openstreetmap.org/{Z}/{X}/{Y}.png', ['a']); 105 | var l = new MM.Layer(p); 106 | 107 | expect(map.insertLayerAt(0, l)).toEqual(map); 108 | expect(map.getLayerAt(0)).toEqual(l); 109 | expect(map.getLayers().length).toEqual(2); 110 | }); 111 | it('Can remove a new layer at 0', function() { 112 | var p = new MM.TemplatedMapProvider( 113 | 'http://{S}.tile.openstreetmap.org/{Z}/{X}/{Y}.png', ['a']); 114 | var l = new MM.Layer(p); 115 | 116 | expect(map.insertLayerAt(0, l)).toEqual(map); 117 | 118 | expect(map.getLayerAt(0)).toEqual(l); 119 | expect(map.getLayers().length).toEqual(2); 120 | 121 | expect(map.removeLayerAt(0)).toEqual(map); 122 | expect(map.getLayers().length).toEqual(1); 123 | }); 124 | 125 | it('Can swap a new layer at 0', function() { 126 | var p = new MM.TemplatedMapProvider( 127 | 'http://{S}.tile.openstreetmap.org/{Z}/{X}/{Y}.png', ['a']); 128 | var l = new MM.Layer(p); 129 | 130 | var l1 = map.getLayerAt(0); 131 | 132 | expect(map.insertLayerAt(1, l)).toEqual(map); 133 | expect(map.swapLayersAt(0, 1)).toEqual(map); 134 | 135 | expect(map.getLayerAt(0)).toEqual(l); 136 | expect(map.getLayerAt(1)).toEqual(l1); 137 | expect(map.getLayers().length).toEqual(2); 138 | }); 139 | 140 | it('Can remove a specific layer', function() { 141 | var p = new MM.TemplatedMapProvider( 142 | 'http://{S}.tile.openstreetmap.org/{Z}/{X}/{Y}.png', ['a']); 143 | var l = new MM.Layer(p); 144 | 145 | expect(map.insertLayerAt(1, l)).toEqual(map); 146 | expect(map.removeLayer(l)).toEqual(map); 147 | 148 | expect(map.getLayers().length).toEqual(1); 149 | }); 150 | }); 151 | 152 | it('can transform an extent into a coord', function() { 153 | expect(map.extentCoordinate([ 154 | { lat: -10, lon: -10 }, 155 | { lat: 10, lon: 10 }])).toEqual(new MM.Coordinate(8, 8, 4)); 156 | }); 157 | 158 | it('binds and calls resized', function() { 159 | spyOn(sink, 'receive'); 160 | map.addCallback('resized', sink.receive); 161 | 162 | runs(function() { 163 | map.setSize({ 164 | x: 200, y: 300 165 | }); 166 | }); 167 | 168 | waits(500); 169 | 170 | runs(function() { 171 | expect(sink.receive).toHaveBeenCalledWith(map, new MM.Point(200, 300)); 172 | }); 173 | }); 174 | 175 | it('can be cleanly destroyed', function() { 176 | map.destroy(); 177 | expect(map.layers.length).toEqual(0); 178 | }); 179 | }); 180 | -------------------------------------------------------------------------------- /src/provider.js: -------------------------------------------------------------------------------- 1 | // Providers 2 | // --------- 3 | // Providers provide tile URLs and possibly elements for layers. 4 | // 5 | // MapProvider -> 6 | // TemplatedMapProvider 7 | // 8 | MM.MapProvider = function(getTile) { 9 | if (getTile) { 10 | this.getTile = getTile; 11 | } 12 | }; 13 | 14 | MM.MapProvider.prototype = { 15 | 16 | // these are limits for available *tiles* 17 | // panning limits will be different (since you can wrap around columns) 18 | // but if you put Infinity in here it will screw up sourceCoordinate 19 | tileLimits: [ 20 | new MM.Coordinate(0,0,0), // top left outer 21 | new MM.Coordinate(1,1,0).zoomTo(18) // bottom right inner 22 | ], 23 | 24 | getTileUrl: function(coordinate) { 25 | throw "Abstract method getTileUrl not implemented by subclass."; 26 | }, 27 | 28 | getTile: function(coordinate) { 29 | throw "Abstract method getTile not implemented by subclass."; 30 | }, 31 | 32 | // releaseTile is not required 33 | releaseTile: function(element) { }, 34 | 35 | // use this to tell MapProvider that tiles only exist between certain zoom levels. 36 | // should be set separately on Map to restrict interactive zoom/pan ranges 37 | setZoomRange: function(minZoom, maxZoom) { 38 | this.tileLimits[0] = this.tileLimits[0].zoomTo(minZoom); 39 | this.tileLimits[1] = this.tileLimits[1].zoomTo(maxZoom); 40 | }, 41 | 42 | // return null if coord is above/below row extents 43 | // wrap column around the world if it's outside column extents 44 | // ... you should override this function if you change the tile limits 45 | // ... see enforce-limits in examples for details 46 | sourceCoordinate: function(coord) { 47 | var TL = this.tileLimits[0].zoomTo(coord.zoom), 48 | BR = this.tileLimits[1].zoomTo(coord.zoom), 49 | columnSize = Math.pow(2, coord.zoom), 50 | wrappedColumn; 51 | 52 | if (coord.column < 0) { 53 | wrappedColumn = (coord.column + columnSize) % columnSize; 54 | } else { 55 | wrappedColumn = coord.column % columnSize; 56 | } 57 | 58 | if (coord.row < TL.row || coord.row >= BR.row) { 59 | return null; 60 | } else if (wrappedColumn < TL.column || wrappedColumn >= BR.column) { 61 | return null; 62 | } else { 63 | return new MM.Coordinate(coord.row, wrappedColumn, coord.zoom); 64 | } 65 | } 66 | }; 67 | 68 | /** 69 | * FIXME: need a better explanation here! This is a pretty crucial part of 70 | * understanding how to use ModestMaps. 71 | * 72 | * TemplatedMapProvider is a tile provider that generates tile URLs from a 73 | * template string by replacing the following bits for each tile 74 | * coordinate: 75 | * 76 | * {Z}: the tile's zoom level (from 1 to ~20) 77 | * {X}: the tile's X, or column (from 0 to a very large number at higher 78 | * zooms) 79 | * {Y}: the tile's Y, or row (from 0 to a very large number at higher 80 | * zooms) 81 | * 82 | * E.g.: 83 | * 84 | * var osm = new MM.TemplatedMapProvider("http://tile.openstreetmap.org/{Z}/{X}/{Y}.png"); 85 | * 86 | * Or: 87 | * 88 | * var placeholder = new MM.TemplatedMapProvider("http://placehold.it/256/f0f/fff.png&text={Z}/{X}/{Y}"); 89 | * 90 | */ 91 | MM.TemplatedMapProvider = function(template, subdomains) { 92 | var isQuadKey = template.match(/{(Q|quadkey)}/); 93 | // replace Microsoft style substitution strings 94 | if (isQuadKey) template = template 95 | .replace('{subdomains}', '{S}') 96 | .replace('{zoom}', '{Z}') 97 | .replace('{quadkey}', '{Q}'); 98 | 99 | var hasSubdomains = (subdomains && 100 | subdomains.length && template.indexOf("{S}") >= 0); 101 | 102 | var getTileUrl = function(coordinate) { 103 | var coord = this.sourceCoordinate(coordinate); 104 | if (!coord) { 105 | return null; 106 | } 107 | var base = template; 108 | if (hasSubdomains) { 109 | var index = parseInt(coord.zoom + coord.row + coord.column, 10) % 110 | subdomains.length; 111 | base = base.replace('{S}', subdomains[index]); 112 | } 113 | if (isQuadKey) { 114 | return base 115 | .replace('{Z}', coord.zoom.toFixed(0)) 116 | .replace('{Q}', this.quadKey(coord.row, 117 | coord.column, 118 | coord.zoom)); 119 | } else { 120 | return base 121 | .replace('{Z}', coord.zoom.toFixed(0)) 122 | .replace('{X}', coord.column.toFixed(0)) 123 | .replace('{Y}', coord.row.toFixed(0)); 124 | } 125 | }; 126 | 127 | MM.MapProvider.call(this, getTileUrl); 128 | }; 129 | 130 | MM.TemplatedMapProvider.prototype = { 131 | // quadKey generator 132 | quadKey: function(row, column, zoom) { 133 | var key = ''; 134 | for (var i = 1; i <= zoom; i++) { 135 | key += (((row >> zoom - i) & 1) << 1) | ((column >> zoom - i) & 1); 136 | } 137 | return key || '0'; 138 | }, 139 | getTile: function(coord) { 140 | return this.getTileUrl(coord); 141 | } 142 | }; 143 | 144 | MM.extend(MM.TemplatedMapProvider, MM.MapProvider); 145 | 146 | MM.TemplatedLayer = function(template, subdomains) { 147 | return new MM.Layer(new MM.TemplatedMapProvider(template, subdomains)); 148 | }; 149 | -------------------------------------------------------------------------------- /test/browser/lib/jasmine-1.2.0.rc3/jasmine.css: -------------------------------------------------------------------------------- 1 | body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; } 2 | 3 | #HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; } 4 | #HTMLReporter a { text-decoration: none; } 5 | #HTMLReporter a:hover { text-decoration: underline; } 6 | #HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; } 7 | #HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; } 8 | #HTMLReporter #jasmine_content { position: fixed; right: 100%; } 9 | #HTMLReporter .version { color: #aaaaaa; } 10 | #HTMLReporter .banner { margin-top: 14px; } 11 | #HTMLReporter .duration { color: #aaaaaa; float: right; } 12 | #HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; } 13 | #HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; } 14 | #HTMLReporter .symbolSummary li.passed { font-size: 14px; } 15 | #HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; } 16 | #HTMLReporter .symbolSummary li.failed { line-height: 9px; } 17 | #HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; } 18 | #HTMLReporter .symbolSummary li.skipped { font-size: 14px; } 19 | #HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; } 20 | #HTMLReporter .symbolSummary li.pending { line-height: 11px; } 21 | #HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; } 22 | #HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; } 23 | #HTMLReporter .runningAlert { background-color: #666666; } 24 | #HTMLReporter .skippedAlert { background-color: #aaaaaa; } 25 | #HTMLReporter .skippedAlert:first-child { background-color: #333333; } 26 | #HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; } 27 | #HTMLReporter .passingAlert { background-color: #a6b779; } 28 | #HTMLReporter .passingAlert:first-child { background-color: #5e7d00; } 29 | #HTMLReporter .failingAlert { background-color: #cf867e; } 30 | #HTMLReporter .failingAlert:first-child { background-color: #b03911; } 31 | #HTMLReporter .results { margin-top: 14px; } 32 | #HTMLReporter #details { display: none; } 33 | #HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; } 34 | #HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; } 35 | #HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; } 36 | #HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; } 37 | #HTMLReporter.showDetails .summary { display: none; } 38 | #HTMLReporter.showDetails #details { display: block; } 39 | #HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; } 40 | #HTMLReporter .summary { margin-top: 14px; } 41 | #HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; } 42 | #HTMLReporter .summary .specSummary.passed a { color: #5e7d00; } 43 | #HTMLReporter .summary .specSummary.failed a { color: #b03911; } 44 | #HTMLReporter .description + .suite { margin-top: 0; } 45 | #HTMLReporter .suite { margin-top: 14px; } 46 | #HTMLReporter .suite a { color: #333333; } 47 | #HTMLReporter #details .specDetail { margin-bottom: 28px; } 48 | #HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; } 49 | #HTMLReporter .resultMessage { padding-top: 14px; color: #333333; } 50 | #HTMLReporter .resultMessage span.result { display: block; } 51 | #HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; } 52 | 53 | #TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ } 54 | #TrivialReporter a:visited, #TrivialReporter a { color: #303; } 55 | #TrivialReporter a:hover, #TrivialReporter a:active { color: blue; } 56 | #TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; } 57 | #TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; } 58 | #TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; } 59 | #TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; } 60 | #TrivialReporter .runner.running { background-color: yellow; } 61 | #TrivialReporter .options { text-align: right; font-size: .8em; } 62 | #TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; } 63 | #TrivialReporter .suite .suite { margin: 5px; } 64 | #TrivialReporter .suite.passed { background-color: #dfd; } 65 | #TrivialReporter .suite.failed { background-color: #fdd; } 66 | #TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; } 67 | #TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; } 68 | #TrivialReporter .spec.failed { background-color: #fbb; border-color: red; } 69 | #TrivialReporter .spec.passed { background-color: #bfb; border-color: green; } 70 | #TrivialReporter .spec.skipped { background-color: #bbb; } 71 | #TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; } 72 | #TrivialReporter .passed { background-color: #cfc; display: none; } 73 | #TrivialReporter .failed { background-color: #fbb; } 74 | #TrivialReporter .skipped { color: #777; background-color: #eee; display: none; } 75 | #TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; } 76 | #TrivialReporter .resultMessage .mismatch { color: black; } 77 | #TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; } 78 | #TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; } 79 | #TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; } 80 | #TrivialReporter #jasmine_content { position: fixed; right: 100%; } 81 | #TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; } 82 | -------------------------------------------------------------------------------- /src/mouse.js: -------------------------------------------------------------------------------- 1 | // Event Handlers 2 | // -------------- 3 | 4 | // A utility function for finding the offset of the 5 | // mouse from the top-left of the page 6 | MM.getMousePoint = function(e, map) { 7 | // start with just the mouse (x, y) 8 | var point = new MM.Point(e.clientX, e.clientY); 9 | 10 | // correct for scrolled document 11 | point.x += document.body.scrollLeft + document.documentElement.scrollLeft; 12 | point.y += document.body.scrollTop + document.documentElement.scrollTop; 13 | 14 | // correct for nested offsets in DOM 15 | for (var node = map.parent; node; node = node.offsetParent) { 16 | point.x -= node.offsetLeft; 17 | point.y -= node.offsetTop; 18 | } 19 | return point; 20 | }; 21 | 22 | // A handler that allows mouse-wheel zooming - zooming in 23 | // when page would scroll up, and out when the page would scroll down. 24 | MM.MouseWheelHandler = function(map, precise) { 25 | // only init() if we get a map 26 | if (map) { 27 | this.init(map, precise); 28 | // allow (null, true) as constructor args 29 | } else if (arguments.length > 1) { 30 | this.precise = precise ? true : false; 31 | } 32 | }; 33 | 34 | MM.MouseWheelHandler.prototype = { 35 | precise: false, 36 | 37 | init: function(map) { 38 | this.map = map; 39 | this._mouseWheel = MM.bind(this.mouseWheel, this); 40 | 41 | this._zoomDiv = document.body.appendChild(document.createElement('div')); 42 | this._zoomDiv.style.cssText = 'visibility:hidden;top:0;height:0;width:0;overflow-y:scroll'; 43 | var innerDiv = this._zoomDiv.appendChild(document.createElement('div')); 44 | innerDiv.style.height = '2000px'; 45 | MM.addEvent(map.parent, 'mousewheel', this._mouseWheel); 46 | }, 47 | 48 | remove: function() { 49 | MM.removeEvent(this.map.parent, 'mousewheel', this._mouseWheel); 50 | this._zoomDiv.parentNode.removeChild(this._zoomDiv); 51 | }, 52 | 53 | mouseWheel: function(e) { 54 | var delta = 0; 55 | this.prevTime = this.prevTime || new Date().getTime(); 56 | 57 | try { 58 | this._zoomDiv.scrollTop = 1000; 59 | this._zoomDiv.dispatchEvent(e); 60 | delta = 1000 - this._zoomDiv.scrollTop; 61 | } catch (error) { 62 | delta = e.wheelDelta || (-e.detail * 5); 63 | } 64 | 65 | // limit mousewheeling to once every 200ms 66 | var timeSince = new Date().getTime() - this.prevTime; 67 | 68 | if (Math.abs(delta) > 0 && (timeSince > 200) && !this.precise) { 69 | var point = MM.getMousePoint(e, this.map); 70 | this.map.zoomByAbout(delta > 0 ? 1 : -1, point); 71 | 72 | this.prevTime = new Date().getTime(); 73 | } else if (this.precise) { 74 | var point = MM.getMousePoint(e, this.map); 75 | this.map.zoomByAbout(delta * 0.001, point); 76 | } 77 | 78 | // Cancel the event so that the page doesn't scroll 79 | return MM.cancelEvent(e); 80 | } 81 | }; 82 | 83 | // Handle double clicks, that zoom the map in one zoom level. 84 | MM.DoubleClickHandler = function(map) { 85 | if (map !== undefined) { 86 | this.init(map); 87 | } 88 | }; 89 | 90 | MM.DoubleClickHandler.prototype = { 91 | 92 | init: function(map) { 93 | this.map = map; 94 | this._doubleClick = MM.bind(this.doubleClick, this); 95 | MM.addEvent(map.parent, 'dblclick', this._doubleClick); 96 | }, 97 | 98 | remove: function() { 99 | MM.removeEvent(this.map.parent, 'dblclick', this._doubleClick); 100 | }, 101 | 102 | doubleClick: function(e) { 103 | // Ensure that this handler is attached once. 104 | // Get the point on the map that was double-clicked 105 | var point = MM.getMousePoint(e, this.map); 106 | 107 | // use shift-double-click to zoom out 108 | this.map.zoomByAbout(e.shiftKey ? -1 : 1, point); 109 | 110 | return MM.cancelEvent(e); 111 | } 112 | }; 113 | 114 | // Handle the use of mouse dragging to pan the map. 115 | MM.DragHandler = function(map) { 116 | if (map !== undefined) { 117 | this.init(map); 118 | } 119 | }; 120 | 121 | MM.DragHandler.prototype = { 122 | 123 | init: function(map) { 124 | this.map = map; 125 | this._mouseDown = MM.bind(this.mouseDown, this); 126 | MM.addEvent(map.parent, 'mousedown', this._mouseDown); 127 | }, 128 | 129 | remove: function() { 130 | MM.removeEvent(this.map.parent, 'mousedown', this._mouseDown); 131 | }, 132 | 133 | mouseDown: function(e) { 134 | MM.addEvent(document, 'mouseup', this._mouseUp = MM.bind(this.mouseUp, this)); 135 | MM.addEvent(document, 'mousemove', this._mouseMove = MM.bind(this.mouseMove, this)); 136 | 137 | this.prevMouse = new MM.Point(e.clientX, e.clientY); 138 | this.map.parent.style.cursor = 'move'; 139 | 140 | return MM.cancelEvent(e); 141 | }, 142 | 143 | mouseMove: function(e) { 144 | if (this.prevMouse) { 145 | this.map.panBy( 146 | e.clientX - this.prevMouse.x, 147 | e.clientY - this.prevMouse.y); 148 | this.prevMouse.x = e.clientX; 149 | this.prevMouse.y = e.clientY; 150 | this.prevMouse.t = +new Date(); 151 | } 152 | 153 | return MM.cancelEvent(e); 154 | }, 155 | 156 | mouseUp: function(e) { 157 | MM.removeEvent(document, 'mouseup', this._mouseUp); 158 | MM.removeEvent(document, 'mousemove', this._mouseMove); 159 | 160 | this.prevMouse = null; 161 | this.map.parent.style.cursor = ''; 162 | 163 | return MM.cancelEvent(e); 164 | } 165 | }; 166 | 167 | // A shortcut for adding drag, double click, 168 | // and mouse wheel events to the map. This is the default 169 | // handler attached to a map if the handlers argument isn't given. 170 | MM.MouseHandler = function(map) { 171 | if (map !== undefined) { 172 | this.init(map); 173 | } 174 | }; 175 | 176 | MM.MouseHandler.prototype = { 177 | init: function(map) { 178 | this.map = map; 179 | this.handlers = [ 180 | new MM.DragHandler(map), 181 | new MM.DoubleClickHandler(map), 182 | new MM.MouseWheelHandler(map) 183 | ]; 184 | }, 185 | remove: function() { 186 | for (var i = 0; i < this.handlers.length; i++) { 187 | this.handlers[i].remove(); 188 | } 189 | } 190 | }; 191 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | // Make inheritance bearable: clone one level of properties 2 | MM.extend = function(child, parent) { 3 | for (var property in parent.prototype) { 4 | if (typeof child.prototype[property] == "undefined") { 5 | child.prototype[property] = parent.prototype[property]; 6 | } 7 | } 8 | return child; 9 | }; 10 | 11 | MM.getFrame = function () { 12 | // native animation frames 13 | // http://webstuff.nfshost.com/anim-timing/Overview.html 14 | // http://dev.chromium.org/developers/design-documents/requestanimationframe-implementation 15 | // http://paulirish.com/2011/requestanimationframe-for-smart-animating/ 16 | // can't apply these directly to MM because Chrome needs window 17 | // to own webkitRequestAnimationFrame (for example) 18 | // perhaps we should namespace an alias onto window instead? 19 | // e.g. window.mmRequestAnimationFrame? 20 | return function(callback) { 21 | (window.requestAnimationFrame || 22 | window.webkitRequestAnimationFrame || 23 | window.mozRequestAnimationFrame || 24 | window.oRequestAnimationFrame || 25 | window.msRequestAnimationFrame || 26 | function (callback) { 27 | window.setTimeout(function () { 28 | callback(+new Date()); 29 | }, 10); 30 | })(callback); 31 | }; 32 | }(); 33 | 34 | // Inspired by LeafletJS 35 | MM.transformProperty = (function(props) { 36 | if (!this.document) return; // node.js safety 37 | var style = document.documentElement.style; 38 | for (var i = 0; i < props.length; i++) { 39 | if (props[i] in style) { 40 | return props[i]; 41 | } 42 | } 43 | return false; 44 | })(['transformProperty', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']); 45 | 46 | MM.matrixString = function(point) { 47 | // Make the result of point.scale * point.width a whole number. 48 | if (point.scale * point.width % 1) { 49 | point.scale += (1 - point.scale * point.width % 1) / point.width; 50 | } 51 | 52 | var scale = point.scale || 1; 53 | if (MM._browser.webkit3d) { 54 | // return 'scale3d(' + scale + ',' + scale + ', 1) translate3d(' + point.x.toFixed(6) + 'px,' + point.y.toFixed(6) + 'px, 0px)'; 55 | return 'scale3d(' + scale + ',' + scale + ', 1) translate3d(' + point.x.toFixed(0) + 'px,' + point.y.toFixed(0) + 'px, 0px)'; 56 | } else { 57 | return 'scale(' + scale + ',' + scale + ') translate(' + point.x.toFixed(6) + 'px,' + point.y.toFixed(6) + 'px)'; 58 | } 59 | }; 60 | 61 | MM._browser = (function(window) { 62 | return { 63 | webkit: ('WebKitCSSMatrix' in window), 64 | webkit3d: ('WebKitCSSMatrix' in window) && ('m11' in new WebKitCSSMatrix()) 65 | }; 66 | })(this); // use this for node.js global 67 | 68 | MM.moveElement = function(el, point) { 69 | if (MM.transformProperty) { 70 | // Optimize for identity transforms, where you don't actually 71 | // need to change this element's string. Browsers can optimize for 72 | // the .style.left case but not for this CSS case. 73 | if (!point.scale) point.scale = 1; 74 | if (!point.width) point.width = 0; 75 | if (!point.height) point.height = 0; 76 | var ms = MM.matrixString(point); 77 | if (el[MM.transformProperty] !== ms) { 78 | el.style[MM.transformProperty] = 79 | el[MM.transformProperty] = ms; 80 | } 81 | } else { 82 | el.style.left = point.x + 'px'; 83 | el.style.top = point.y + 'px'; 84 | // Don't set width unless asked to: this is performance-intensive 85 | // and not always necessary 86 | if (point.width && point.height && point.scale) { 87 | el.style.width = Math.ceil(point.width * point.scale) + 'px'; 88 | el.style.height = Math.ceil(point.height * point.scale) + 'px'; 89 | } 90 | } 91 | }; 92 | 93 | // Events 94 | // Cancel an event: prevent it from bubbling 95 | MM.cancelEvent = function(e) { 96 | // there's more than one way to skin this cat 97 | e.cancelBubble = true; 98 | e.cancel = true; 99 | e.returnValue = false; 100 | if (e.stopPropagation) { e.stopPropagation(); } 101 | if (e.preventDefault) { e.preventDefault(); } 102 | return false; 103 | }; 104 | 105 | // From underscore.js 106 | MM.bind = function(func, obj) { 107 | var slice = Array.prototype.slice; 108 | var nativeBind = Function.prototype.bind; 109 | if (func.bind === nativeBind && nativeBind) { 110 | return nativeBind.apply(func, slice.call(arguments, 1)); 111 | } 112 | var args = slice.call(arguments, 2); 113 | return function() { 114 | return func.apply(obj, args.concat(slice.call(arguments))); 115 | }; 116 | }; 117 | 118 | MM.coerceLayer = function(layerish) { 119 | if (typeof layerish == 'string') { 120 | // Probably a template string 121 | return new MM.Layer(new MM.TemplatedMapProvider(layerish)); 122 | } else if ('draw' in layerish && typeof layerish.draw == 'function') { 123 | // good enough, though we should probably enforce .parent and .destroy() too 124 | return layerish; 125 | } else { 126 | // probably a MapProvider 127 | return new MM.Layer(layerish); 128 | } 129 | }; 130 | 131 | // see http://ejohn.org/apps/jselect/event.html for the originals 132 | MM.addEvent = function(obj, type, fn) { 133 | if (obj.addEventListener) { 134 | obj.addEventListener(type, fn, false); 135 | if (type == 'mousewheel') { 136 | obj.addEventListener('DOMMouseScroll', fn, false); 137 | } 138 | } else if (obj.attachEvent) { 139 | obj['e'+type+fn] = fn; 140 | obj[type+fn] = function(){ obj['e'+type+fn](window.event); }; 141 | obj.attachEvent('on'+type, obj[type+fn]); 142 | } 143 | }; 144 | 145 | MM.removeEvent = function( obj, type, fn ) { 146 | if (obj.removeEventListener) { 147 | obj.removeEventListener(type, fn, false); 148 | if (type == 'mousewheel') { 149 | obj.removeEventListener('DOMMouseScroll', fn, false); 150 | } 151 | } else if (obj.detachEvent) { 152 | obj.detachEvent('on'+type, obj[type+fn]); 153 | obj[type+fn] = null; 154 | } 155 | }; 156 | 157 | // Cross-browser function to get current element style property 158 | MM.getStyle = function(el,styleProp) { 159 | if (el.currentStyle) 160 | return el.currentStyle[styleProp]; 161 | else if (window.getComputedStyle) 162 | return document.defaultView.getComputedStyle(el,null).getPropertyValue(styleProp); 163 | }; 164 | --------------------------------------------------------------------------------