modestmaps-js-3.3.6/000077500000000000000000000000001210227260500143065ustar00rootroot00000000000000modestmaps-js-3.3.6/.gitignore000066400000000000000000000000311210227260500162700ustar00rootroot00000000000000node_modules .swp .*.swp modestmaps-js-3.3.6/CHANGELOG000066400000000000000000000272301210227260500155240ustar00rootroot00000000000000Modest Maps JS Changelog. Following the semantic versioning recommendation best we can: "Consider a version format of X.Y.Z (Major.Minor.Patch). Bug fixes not affecting the API increment the patch version, backwards compatible API additions/changes increment the minor version, and backwards incompatible API changes increment the major version." -- http://semver.org/ v3.3.6 - .enable() and .disable() return the layer object as expected - `setCenter()` now correctly calls enforceLimits so that `locationPoint` is immediately accurate. - Examples fixes - Now builds with uglifyjs instead of YUICompressor v3.3.5 - Fixes rounding bug in MapProvider's `sourceCoordinate` v3.3.4 - Layers no longer copy their CSS properties from their parent nodes, so that parent node CSS can be changed with properties like `clip` and `opacity` without this having per-zoom-level effects. v3.3.3 - Fixes empty gif image v3.3.2 - Fixes problem with parent tiles by again assigning the element to have an empty image as its src. v3.3.1 - Fixes bug where Layer.draw would try to draw even if not attached to a map. v3.3.0 - Adds `.enable()` and `.disable()` methods to layers, and `.enableLayer()`/`.enableLayerAt()` and `.disableLayer()`/`.disableLayerAt()` to the map object. v3.2.1 - Fixes bug in the MM.MapProvider.sourceCoordinate where tiles containing areas within the tile limits were not loaded at lower zoom levels v3.2.0 - Adds named layers: a third parameter to MM.Layer and MM.TemplatedLayer that sets the `.name` property of a layer. The layer name can be used with the new `map.getLayer()` method, which returns the first layer matching that name, and `map.removeLayer()`, which now accepts either the literal layer object or the layer name as its one argument. - Tests have moved from `/test/browser` to just `/test` v3.1.3 - Explicitly set the size of tiles to the `tileSize` parameter in the Map object in order to support tiles that are not 256x256 v3.1.2 - Fixes a bug in the TouchHandler leading to inaccurate movement when on scalable devices like iPhones and not at native screen resolution. v3.1.1 - Fixes a bug in the `MM.Map` constructor which prevent `null` or `undefined` being passed as the layers parameter. This makes it easier to call `MM.Map` with only one argument - the div. v3.1.0 - `map.setZoomRange()` returns the map object to allow it to be chained with other calls. It previously returned undefined. v3.0.0 - Instead of a tile URL or element, the message of the `requesterror` event will be an object with `url` and `element` members relating to the source element and its url / href v2.0.2 - Allows `touchstart` events to propagate to handlers instead of stopping them at the TouchHandler level. This allows libraries that have elements on the map that rely on `click` events to work. This does not fix the problem of allowing scrollable elements in the map element. v2.0.1 - Fixing negative coordinate wrapping, identified at https://github.com/mapbox/tilemill/issues/1395 v2.0.0 - Two breaking changes: - requesterror now calls its callbacks with the img element, like requestcomplete, not img.src as before - TemplatedMapLayer renamed to Template, refs #14 v1.1.3 - Fix Location.bearing method v1.1.2 - Update addMap API to better deal with layers that do deferred `draw` calls - it now requests a redraw if the map has a center v1.1.1 - Fix TouchHandler refactor v1.1.0 - Revert futuristic layer-tile handling to cross-browser version that touches all tiles on all moves. v1.0.3 - Fixes insertLayerAt behavior with indexes within the layers array. Adds associated tests. v1.0.2 - Removes cache infrastructure - Rewrites handlers to remove bind usage, now depends on browser cache - Adds MM.Location.bearing v1.0.0-beta1 - Fixes coordLimits enforcement - Removes TilePaintingProvider and auto-casting to it in setProvider, updating all examples. v1.0.0 (dev) - `map.setSize` now only accepts an object of the form `{x: 0, y: 0}` or a `MM.Point` object, and it calls its callback with a `MM.Point` argument. - `map.roundZoom` added to the map to allow for the old behavior of `map.setExtent` if desired. - Maps now include `MM.TouchHandler()` by default for touch-device compatibility - `MM.MouseWheelHandler()` now supports a second argument, `precise`, which, when true, will allow intermediate zooms. - MouseWheel events are now normalized to take advantage of acceleration and avoid browser bugs. - `map.setExtent()` now accepts either an array of `MM.Location` objects or an `MM.MapExtent` object, which it converts to an array of locations with `extent.toArray()`. - the `MM.Hash` event handler can set a map's center and zoom from the URL hash (`#{zoom}/{lat}/{lon}`) and update the hash when it changes. A standalone class is provided in `examples/hash/modestmaps.hash.js`. - `MM.TemplatedMapProvider()` now supports quadkey coordinate substitutions in URL templates with "{Q}" instead of the TMS-style "{Z}", "{X}" and "{Y}". - `MM.TemplatedMapProvider()` also supports Microsoft-style URL templates, which specify subdomain, zoom and quadkey placeholders with "{subdomains}", "{zoom}" and "{quadkey}", respectively. v0.21.0 - Returns `this` from `map.addCallback()`, `map.removeCallback()`, and `map.dispatchCallback()` v0.20.0 - Adds `map.destroy()` method, and in that process, allows all handlers to be removable with a `.remove()` function. v0.19.1 - Removes unused 'parent' argument to `com.modestmaps.RequestManager` v0.19.0 - Adds a second parameter to `map.setExtent()` which, if true, allows the extent set to place the map at a non-integer zoom level. By default, `setEvent` will behave as before. v0.18.5 - Address issue where display:none on parent affects size detection (thanks @tmcw) - Remove arbitary default map size - parent size is used always unless dimensions are specified. Use setSize to override at any time. v0.18.4 - Fix Location.interpolate between the same location - Fix DragHandler - remove inertia code v0.18.3 - enforce limits when zooming and panning so that map functions are correct when returning and before the deferred draw call using getFrame v0.18.2 - revert to @tmcw's original getFrame (simpler version broke in Chrome) v0.18.1 - revert Coordinate.toKey to string joining - fix up utils.js functions so that tests run again - modify double-tap thresholds and adjust onPinched behavior - a few more cleanups in TouchHandler (mainly to use MM.Point functions) v0.18.0 - many tiny formatting/syntax fixes; thx JSHint! - MM.moveElement is used for tile positioning - MM.moveElement detects and uses CSS transforms wherever possible - MM.bind is used for function binding instead of awkward closures - MM.getFrame is used to request redraws - MM.getFrame uses provisional requestAnimationFrame implementations if possible - TouchHandler is in the default build of modestmaps.js - Coordinate.toKey() is clever again ;) Thanks to @tmcw for the majority of these contributions. They were contributed on a branch, hence only one minor version number bump. As far as we know this should all be backwards compatible. If we're mistaken, please file an issue at https://github.com/stamen/modestmaps-js/issues Note also that we've now got fairly decent node.js support for the core MM.js classes, and there's an example of how to use Modest Maps with node-canvas to render static map images from a tiled data source. "npm install modestmaps" for node.js fans :) v0.17.0 - Mouse handlers refactor: while MouseHandler is still available for adding all handlers to the map, double-click, zoomwheel, and drag handlers are available individually. - Broken images that result from image loading errors are no longer added to the map. v0.16.1 - Unimplemented abstract methods throw exceptions instead of calling `alert()` v0.16.0 - added MapProvider.setZoomRange for @straup v0.15.2 - misc syntax fixes and improve parseInt/radix correctness, from @tmcw v0.15.1 - switched to document fragment for loadingBay, from @tmcw v0.15.0 - added method chaining from @tmcw v0.14.3 - improve redraw behavior by ensuring layer is visible in getTileComplete - use a closure in v0.14.2's setTimeout to ensure proper 'this' v0.14.2 - add setTimeout to processQueue to avoid stack overflow/recursion bug in IE 7/8 (https://github.com/stamen/modestmaps-js/issues/12) thanks @yhahn! v0.14.1 - reinstated display of children for missing tiles, except when if map.enablePyramidLoading is set to true v0.14.0 - added map.enablePyramidLoading flag for pyramid loading (off by default) - fixed Coordinate.prototype.toKey (back to string join to avoid collisions) v0.13.5 - changed the order of initialization so that event handlers go last (this is so that attributes like layerParent are in place for complex event handlers like anyzoom.js... handlers can also use callbacks now) 2011-01-03 note: - broke modestmap.js file into src folder, now building with Makefile - no functionality change to modestmaps.js (just whitespace changes) v0.13.4 - changed to img.style.width instead of img.width, for ipads (see examples/touch/test.html) v0.13.3 - stubbed out tilePadding into Map.draw v0.13.2 - removing magic numbers from Mercator projection for parity with Python version v0.13.1 - rejiggered the Map's draw function to be a bit clearer - removed superfluous layer.coordinate (internal only) v0.13.0 - factored image loading out into a separate RequestManager - cleaned up RequestManager to be less tile-centric, more img-centric v0.12.0 - made callback handling more modular v0.11.2 - re-instated zoom level check for setExtent (fixes bug where locations are all the same) v0.11.1 - moved to cssText for long CSS inits v0.11.0 - added 'drawn' callback - added removeCallback - correctly following semver.org now, incrementing minor version! v0.10.4 - modified queue sorting to support pyramid loading - stubbed out pyramid loading - tidied up draw code to be clearer (wantedTiles --> validTileKeys) v0.10.3 - added Point.distance and Point.interpolate - added Location.distance and Location.interpolate v0.10.2 - tweak to sorting function that appears to fix an issue in IE8 v0.10.1 - fixed tile sorting for in-between zoom levels - fixed tile position in onload handler v0.10.0 - tidied up initial coord/position maths, now supports arbitrary zoom levels (with seams) v0.9.5 v0.9.4 - changes to MapProvider.sourceCoordinate to support non-Mercator bounds v0.9.3 - added inner and outer limits to providers and enforcing min/max zoom in Map.draw() - changed zoomByAbout to use zoomBy (and draw/enforceLimits) before applying panBy v0.9.2 - fixed bug that could break zooming if setCenterZoom was called with a string v0.9.1 - removed assumption that layers go from 0 to 20 v0.9.0 - added rational version numbering May 2010, pre-semver + added setSize and setProvider methods (fixed the latter so that all loads are canceled) + optional interaction (factored out mouse handling... enables touch or keyboard later) + made a touch handler for iphads + made a TemplatedMapProvider + made a demo that accepts a URL template for a map provider + started to work towards jslint conformance + started to move to 80 character line length where practical + made a demo with two maps, synchronized + jslint fixes, 80 char lines, better sorting, removed createOverlay + templated providers wrap around in longitude by default + added a demo keyboard handler + positioning single tiles in onload instead of redrawing everything + added a zoom box demo + make sure the cache gets cleared after a while (BIG FEATURE) + don't load above/below north/south poles (for default mercator maps) + added a random subdomain helper to templated providers modestmaps-js-3.3.6/Makefile000066400000000000000000000015431210227260500157510ustar00rootroot00000000000000NODE_PATH ?= ./node_modules JS_COMPILER = $(NODE_PATH)/uglify-js/bin/uglifyjs JS_FILES = \ src/start.js \ src/utils.js \ src/point.js \ src/coordinate.js \ src/location.js \ src/extent.js \ src/transformation.js \ src/projection.js \ src/provider.js \ src/mouse.js \ src/touch.js \ src/callbacks.js \ src/requests.js \ src/layer.js \ src/map.js \ src/convenience.js \ src/end.js VERSION = `cat VERSION` all: update-version modestmaps.js modestmaps.min.js modestmaps.min.js: modestmaps.js rm -f modestmaps.min.js $(JS_COMPILER) modestmaps.js > modestmaps.min.js modestmaps.js: $(JS_FILES) Makefile rm -f modestmaps.js cat $(JS_FILES) | perl -pi -e "s/{VERSION}/$(VERSION)/g" > modestmaps.js update-version: perl -pi -e "s/\"version\": \"[^\"]+/\"version\": \"$(VERSION)/g" package.json clean: rm -f modestmaps.js rm -f modestmaps.min.js modestmaps-js-3.3.6/README.md000066400000000000000000000021611210227260500155650ustar00rootroot00000000000000Modest Maps JS is a BSD-licensed display and interaction library for tile-based maps in Javascript. Our intent is to provide a minimal, extensible, customizable, and free display library for discriminating designers and developers who want to use interactive maps in their own projects. Modest Maps provides a core set of features in a tight, clean package, with plenty of hooks for additional functionality. # [Documentation](https://github.com/modestmaps/modestmaps-js/wiki) ## Building This package includes a copy of [YUICompressor](http://developer.yahoo.com/yui/compressor/), which requires a version of Java on your system. To create a new build of Modest Maps (only necessary for development), run `make` from the root directory. ## Developing with npm: Modest Maps includes a `package.json` file to guide usage of its code on the server-side, and to handle certain dependencies. To install developer dependencies - needed for documentation and tests - you'll need [npm](http://npmjs.org/): npm install --dev ## Tests Tests require `expresso` to be installed by `npm`, as noted above. To run tests, make tests modestmaps-js-3.3.6/VERSION000066400000000000000000000000061210227260500153520ustar00rootroot000000000000003.3.6 modestmaps-js-3.3.6/doc/000077500000000000000000000000001210227260500150535ustar00rootroot00000000000000modestmaps-js-3.3.6/doc/.gitignore000066400000000000000000000000221210227260500170350ustar00rootroot00000000000000api.html toc.html modestmaps-js-3.3.6/doc/Makefile000066400000000000000000000005351210227260500165160ustar00rootroot00000000000000# to install marked: `npm install marked` MARKED ?= ../node_modules/marked/bin/marked --gfm VERSION = `cat ../VERSION` all: index.html index.html: toc.html api.html cat start.html toc.html middle.html api.html end.html \ | perl -pi -e "s/{VERSION}/$(VERSION)/g" > $@ %.html: %.md $(MARKED) $< > $@ clean: rm -f index.html toc.html api.html modestmaps-js-3.3.6/doc/api.md000066400000000000000000001361701210227260500161560ustar00rootroot00000000000000# modestmaps.js Modest Maps is a bare-bones geographic map display and interaction library. It was designed and conceived by [Tom Carden], tweaked by [Michal Migurski], and is maintained and hacked on primarily by [Tom MacWright] and [Shawn Allen]. [Tom Carden]: http://www.tom-carden.co.uk/ [Michal Migurski]: http://mike.teczno.com/ [Tom MacWright]: http://macwright.org/ [Shawn Allen]: http://github.com/shawnbot ## Getting the Source You can get modestmaps.js by cloning [the github repo](https://github.com/modestmaps/modestmaps-js/) or downloading it: * [modestmaps.js-v{VERSION}.zip](https://github.com/modestmaps/modestmaps-js/zipball/v{VERSION}) (zip archive) * [modestmaps.js](../modestmaps.js) (source JavaScript, 100K) * [modestmaps.min.js](../modestmaps.min.js) (minified JavaScript, 36K) After you've downloaded it, you can include by putting this ` ``` ## Quick Start ### Making a Map Making a map is easy. First, you'll need a place for your map in the HTML: ```
``` Then, add a script below: ``` ``` This will create a map showing [Stamen's toner tiles](http://maps.stamen.com/toner/) in a 640x480-pixel area. See the docs below for more info on [layers](#TemplatedLayer) and [map dimensions](#Map.dimensions). If you want the size of your map to be determined by CSS, you can style your div like so: ``` #map { height: 500px; } ``` Then, leave off the `size` argument to the constructor: ``` ``` For more info on resizing, see the [Map constructor](#Map.constructor) and [autoSize](#Map.autoSize) docs. ### Moving Around Once you've got your map initialized, you can move it around the world. Geographic locations in modestmaps.js are modeled with the [Location](#Location) class, which is called with *latitude* and *longitude* values: ``` var oakland = new MM.Location(37.804, -122.271); ``` Once you've got a location, you can tell the map to center on it by calling [setCenter](#Map.setCenter): ``` // center on Oakland, California map.setCenter(oakland); // center on Amsterdam, Netherlands map.setCenter(new MM.Location(52.3702157, 4.8951679)); ``` You can get the current center of the map with [getCenter](#Map.getCenter). You can also modify the visible area by setting its [extent](#Extent), which is defined by two or more locations. [Setting](#Map.setCenter) a map's extent adjusts its center and zoom level so that the rectangular bounding box surrounding the locations is entirely visible: ``` var oakland = new MM.Location(37.804, -122.271), amsterdam = new MM.Location(52.3702157, 4.8951679); var extent = new MM.Extent(oakland, amsterdam); map.setExtent(extent) ``` You can change the map's zoom level with the [zoomIn](#Map.zoomIn), [zoomOut](#Map.zoomOut) and [zoomTo](#Map.zoomTo) methods: ``` map.zoomIn(); // increase zoom by 1 map.zoomOut(); // decrease zoom by 1 map.zoomTo(12); // zoom to level 12 ``` ### Working with Layers One popular feature of many online maps is a "hybrid" style, which overlays satellite imagery with graphical labels. You can achieve this effect in modestmaps.js by creating two [tile layers](#TemplatedLayer), and passing them both to the [Map constructor](#Map.constructor) as an array: ``` var baseURL = "http://tile.stamen.com/", watercolor = new MM.TemplatedLayer(baseURL + "watercolor/{Z}/{X}/{Y}.jpg"), labels = new MM.TemplatedLayer(baseURL + "toner-labels/{Z}/{X}/{Y}.jpg"); var map = new MM.Map("map", [watercolor, labels]); ``` Or you can create the map with one layer, then add the overlay later: ``` var map = new MM.Map("map", watercolor); map.addLayer(labels); ``` See the [addLayer](#Map.addLayer) docs and the [TemplatedLayer](#TemplatedLayer) class reference for more on working with layers. ## MM.Map The Map class is the core of **modestmaps.js**. ``` new MM.Map(parent [, layerOrLayers [, dimensions [, eventHandlers]]]) ``` Creates a new map inside the given **parent** element, containing the specified **layers**, optionally with the specified **dimensions** in pixels and custom **event handlers**. The **Map** constructor arguments are described in detail below: #### parent The parent [element](http://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-745549614) of the map. This is typically a `String` indicating the ID of the element: ``` var map = new MM.Map("map", []); ``` You can also provide an element reference: ``` var firstDiv = new MM.Map(document.querySelector("div.map"), ...); ``` Here's a pattern for inserting maps into a series of classed elements, using [document.getElementsByClassName](https://developer.mozilla.org/en/DOM/document.getElementsByClassName): ``` var elements = document.getElementsByClassName("map"), maps = []; for (var i = 0; i < elements.length; i++) { maps[i] = new MM.Map(elements[i], ...); } ``` If an element with the provided ID string is not found, an exception is raised. If **parent** is not an object or a string, an exception is raised. #### layerOrLayers Either a single [layer](#Layer) object or an array of layer objects. Layer objects should either be instances of the [Layer](#Layer) class or implement the [Layer interface](#Layer-Interface). Here's an example of a map with two tile layers, from maps.stamen.com: ``` var baseURL = "http://tile.stamen.com/", watercolor = new MM.TemplatedLayer(baseURL + "watercolor/{Z}/{X}/{Y}.png"), labels = new MM.TemplatedLayer(baseURL + "toner-labels/{Z}/{X}/{Y}.png"); var map = new MM.Map("map", [watercolor, labels]); ``` NOTE: Before modestmaps.js *v3.1.1*, an exception was raised if **layerOrLayers** was not an object or an array. #### dimensions An optional map size in pixels, expressed as a [Point](#Point) object. ``` var size = new MM.Point(512, 512); var map = new MM.Map("map", [], size); console.log("map size:", map.dimensions.x, "x", map.dimensions.y); ``` If **dimensions** is `null` or `undefined`, the dimensions are derived from the width and height of the parent element, and the map's [autoSize](#Map.autoSize) flag is set to `true`. Empty `
` elements have no intrinsic height, so if you don't provide dimensions you'll need to provide the map's parent height in CSS (either inline or in a stylesheet). #### eventHandlers An optional array of interaction event handlers, which should implement the [EventHandler interface](#EventHandler-Interface). If no handlers are provided (`eventHandlers === undefined`), the map is initialized with [mouse](#MouseHandler) and [touch](#TouchHandler) handlers. For instance, to create a map without scroll wheel zooming (which is enabled by default), you can provide [drag](#DragHandler), and [double-click](#DoubleClickHandler) handlers: ``` var map = new MM.Map("map", [], null, [ new MM.DragHandler(), new MM.DoubleClickHandler() ]); ``` The [touch](#TouchHandler) handler provides panning and zooming in touch-enabled browsers: ``` var map = new MM.Map("map", [], null, [ new MM.TouchHandler() ]); ``` To initialize the map without any interaction handlers, specify **eventHandlers** as `null` or an empty array (`[]`). ### getCenter `map.getCenter()` Get the map's center **location**. ``` var center = map.getCenter(); console.log("center latitude:", center.lat, "+ longitude:", center.lon); ``` ### setCenter `map.setCenter(location)` Set the map's center **location**. ``` var center = new MM.Location(37.764, -122.419); map.setCenter(center); ``` ### getZoom `map.getZoom()` Get the map's **zoom level**. ``` var zoom = map.getZoom(); console.log("zoom level:", zoom); ``` ### setZoom `map.setZoom(zoom)` Set the map's **zoom level**. ``` map.setZoom(17); ``` ### setCenterZoom `map.setCenterZoom(location, zoom)` Set the map's center **location** and **zoom level**. ``` map.setCenterZoom(new MM.Location(37.764, -122.419), 17); ``` ### getExtent `map.getExtent()` Get the visible extent (bounds) of the map as an [Extent](#Extent) object. ``` var extent = map.getExtent(); console.log("northwest location:", extent.northWest()); console.log("southeast location:", extent.southEast()); ``` ### setExtent `map.setExtent(extent [, precise])` Modify the center and zoom of the map so that the provided **extent** is visible. If **precise** is `true`, resulting zoom levels may be fractional. (By default, the map's zoom level is rounded down to keep tile images from blurring.) ``` var extent = new MM.Extent( new MM.Location(55.679, 12.584), new MM.Location(55.668, 12.607) ); map.setExtent(extent, true); ``` NOTE: For historical reasons, **setExtent** also accepts an array of [Location](#Location) objects, which are converted using [Extent.fromArray](#Extent.fromArray). ### zoomIn `map.zoomIn()` Increase the map's zoom level by one. ### zoomOut `map.zoomOut()` Decrease the map's zoom level by one. ### zoomBy `map.zoomBy(zoomOffset)` Zoom the map by the provided **offset**. Positive offsets zoom in; negative offsets zoom out. ``` // this is the equivalent of calling map.zoomIn() twice: map.zoomBy(2); ``` ### zoomByAbout `map.zoomByAbout(zoomOffset, point)` Zoom the map by the provided **zoom offset** from a **point** on the screen in pixels. Positive offsets zoom in; negative offsets zoom out. This function is used by [DoubleClickHandler](#DoubleClickHandler) to zoom in on the point where the map is double-clicked. ``` // zoom in on the upper left corner var point = new MM.Point(0, 0); map.zoomByAbout(1, point); ``` ### panBy `map.panBy(x, y)` Pan the map by the specified **x** and **y** distance in pixels. Positive values move the map right and down, respectively; negative values move the map left and up. ``` // pan 500 pixels to the right map.panBy(500, 0); // pan 200 pixels up map.panBy(0, -200); ``` ### panLeft `map.panLeft()` ### panRight `map.panRight()` ### panUp `map.panUp()` ### panDown `map.panDown()` Pan the map to the left, right, up or down by 100 pixels. To vary the offset distance, use [panBy](#Map.panBy). ### getLayers `map.getLayers()` Get a copy of the map's layers array. ``` var layers = map.getLayers(); var base = layers[0]; ``` ### getLayerAt `map.getLayerAt(index)` Get the layer at a specific **index**. The first layer is at index `0`, the second at `1`, etc. ``` var map = new MM.Map(...); var base = map.getLayerAt(0); base.parent.id = "base"; ``` ### addLayer `map.addLayer(layer)` Add **layer** to the map's [layer stack](#Map.layers)]. This triggers a [redraw](#Map.redraw). ``` var layer = new MM.TemplatedLayer("http://tile.stamen.com/toner-lines/{Z}/{X}/{Y}.png"); map.addLayer(layer); ``` ### removeLayer `map.removeLayer(layer)` Remove **layer** from the map's [layer stack](#Map.layers). ### setLayerAt `map.setLayerAt(index, newLayer)` Replace the existing layer at **index** with the **new layer**. ``` var layer = new MM.TemplatedLayer("http://tile.stamen.com/toner/{Z}/{X}/{Y}.png"); map.setLayerAt(0, layer); ``` ### insertLayerAt `map.insertLayerAt(index, layer)` Insert a **layer** at the provided **index**. ``` // let's assume the map has 2 layers already var layer = new MM.TemplatedLayer("http://tile.stamen.com/toner-lines/{Z}/{X}/{Y}.png"); map.insertLayerAt(1, layer); // (now it has 3, with our new layer at index 1) ``` ### removeLayerAt `map.removeLayerAt(index)` Remove the layer at the provided index. ``` // remove the second layer map.removeLayerAt(1); ``` ### swapLayersAt `map.swapLayersAt(indexA, indexB)` Swap the z-index (order) or the layers at **indexA** and **indexB**. ``` // swap the bottom and top layers var bottom = 0, top = map.getLayers().length - 1; map.swapLayersAt(bottom, top); ``` ### pointLocation `map.pointLocation(screenPoint)` Convert a **point on the screen** to a [location](#Location) (a point on the Earth). ### pointCoordinate `map.pointCoordinate(screenPoint)` Convert a **point on the screen** to a [tile coordinate](#Coordinate). ### locationPoint `locationPoint(location)` Convert a **location** (a point on the Earth) to a [point](#Point) on the screen. ### locationCoordinate `map.locationCoordinate(location)` Convert a **location** (a point on the Earth) to a [tile coordinate](#Coordinate). ### coordinateLocation `map.coordinateLocation(coord)` Convert a [tile coordinate](#Coordinate) to a [location](#Location) (a point on the Earth). ### coordinatePoint `map.coordinatePoint(coord)` Convert a [tile coordinate](#Coordinate) to a [point](#Point) on the screen. ### setZoomRange `map.setZoomRange(minZoom, maxZoom)` Set the map's **minimum** and **maximum** zoom levels. This function modifies the zoom levels of the map's [coordLimits](#Map.coordLimits). ### setSize `map.setSize(dimensions)` Set the map's **dimensions** in pixels. If the map's [autoSize](#Map.autoSize) flag is `true`, setting the size manually sets **autoSize** to `false` and prevents further automatic resizing. ``` map.setSize(new MM.Point(640, 480)); ``` NOTE: The map's current size is available in its [dimensions](#Map.dimensions) property. ### addCallback `map.addCallback(eventType, callback)` Add a **callback function** (listener) to the map for a specific **event type**. Callback functions always receive the map instance as their first argument; additional arguments differ by event type. See the [events list](#Map-events) for supported types. ``` function onPanned(map, offset) { console.log("panned by", offset[0], offset[1]); } map.addCallback("panned", onPanned); ``` You can remove callbacks with [removeCallback](#Map.removeCallback). ### removeCallback `map.removeCallback(eventType, callback)` Remove a **callback function** (listener) for the given **event type**. You can add callbacks with [addCallback](#Map.addCallback). ``` map.removeCallback("panned", onPanned); ``` ### draw `map.draw()` Redraw the map and its layers. First, the map enforces its [coordLimits](#Map.coordLimits) on its center and zoom. If [autoSize](#Map.autoSize) is `true`, the map's dimensions are recalculated from its [parent](#Map.parent). Lastly, each of the map's layers is [drawn](#Layer.draw). ### requestRedraw `map.requestRedraw()` Request a "lazy" call to [draw](#Map.draw) in 1 second. This is useful if you're responding to lots of user input and know that you'll need to redraw the map *eventually*, but not immediately. Multiple calls to **requestRedraw** within 1 second of one another will be ignored, so this is a perfectly reasonable thing to do: ``` setInterval(function() { map.requestRedraw(); }, 100); ``` ## Hybrid Methods Hybrid methods behave differently depending on whether they receive arguments: The "getter" form (with no arguments) returns the current value, and the "setter" form sets it to the value provided then returns `this`, which makes function chaining possible (a la [jQuery](http://jquery.com), [d3](http://d3js.com), and [Polymaps](http://polymaps.org)). ### center `map.center([location])` [Get](#Map.getCenter) or [set](#Map.setCenter) the map's center **location**. ``` var center = map.center(); center.lat += .1; map.center(center); ``` ### zoom `map.zoom([level])` [Get](#Map.getZoom) or [set](#Map.setZoom) the map's **zoom level**. ``` var zoom = map.zoom(); zoom -= 3; map.zoom(zoom); ``` ### extent `map.extent([locationsOrExtent [, precise]])` [Get](#Map.getZoom) or [set](#Map.setZoom) the map's extent. If **precise** is `true`, resulting zoom levels may be fractional. ``` // get the extent, check if it contains a location… var extent = map.extent(), loc = new MM.Location(37.764, -122.419); if (!extent.containsLocation(loc)) { // then enclose the location and set the map's new extent extent.encloseLocation(loc); map.extent(extent); } ``` ## Map Properties ### autoSize `map.autoSize` The **autoSize** property is set to `true` if no dimensions are provided in the [constructor](#Map). When **autoSize** is `true`, the map's dimensions are recalculated (and the map is [redrawn](#Map.draw)) [on window resize](https://developer.mozilla.org/en/DOM/window.onresize). ### coordinate `map.coordinate` The map's current center [coordinate](#Coordinate). ### coordLimits `map.coordLimits` An array specifying the map's coordinate bounds, in which the first element defines the top left (northwest) and outermost zoom level, and the second defines the bottom right (southwest) and innermost zoom. You can adjust the minimum and maximum zoom levels of the map without affecting the bounds with [setZoomRange](#Map.setZoomRange). ### dimensions `map.dimensions` The map's current dimensions, expressed as a [Point](#Point). ``` // the bottom right screen coordinate is also its southeast point var southEast = map.pointLocation(map.dimensions); ``` ### parent `map.parent` The map's parent (container) DOM element. ``` map.parent.style.backgroundColor = "green"; ``` ### projection `map.projection` The map's projection, also known as Coordinate Reference System (CRS) or Spatial Reference System (SRS). ### tileSize `map.tileSize` The pixel dimensions of the map's individual image tiles, expressed as a [Point](#Point). By default, tiles are **256** pixels square. ``` // you can use the tile size to estimate the number of tiles visible // at any given moment: var maxRows = Math.ceil(map.dimensions.x / map.tileSize.x); var maxCols = Math.ceil(map.dimensions.y / map.tileSize.y); var maxTiles = maxRows * maxCols; console.log("max tiles:", maxTiles); ``` ## Map Events Map events are triggered when the map moves (either in response to a direct function call or indirectly, through user interaction with an event handlers) or is [drawn](#Map.draw). You can start and stop listening for map events with [addCallback](#Map.addCallback) and [removeCallback](#Map.removeCallback): ``` function onDrawn(map) { console.log("map drawn!"); } // After this, the onDrawn will be called whenever the map changes. map.addCallback("drawn", onZoomed); // Later, remove the callback. map.removeCallback("drawn", onZoomed); ``` ### "zoomed" `function(map, zoomOffset) { ... }` Fires when the **map's** zoom level changes, usually in response to [zoomBy](#Map.zoomBy) or [zoomByAbout](#Map.zoomByAbout). Note that the **zoom offset** is the *difference* between the last zoom level and the new zoom level. You can query the map's current zoom level (rather than the offset) with [getZoom](#Map.getZoom). ``` map.addCallback("zoomed", function(map, zoomOffset) { console.log("map zoomed by:", zoomOffset); }); ``` ### "panned" `function(map, panOffset) { ... }` Fires when the **map** is panned, and receives the **pan offset** (delta) in pixels as a two-element array (`[dx, dy]`). ``` map.addCallback("panned", function(map, panOffset) { var dx = panOffset[0], dy = panOffset[1]; console.log("map panned by x:", dx, "y:", dy); }); ``` ### "resized" `function(map, dimensions) { ... }` Fires when the **map** is resized, and receives the map's new **dimensions** as a [Point](#Point) object. ``` map.addCallback("panned", function(map, dimensions) { console.log("map dimensions:", dimensions.x, "y:", dimensions.y); }); ``` ### "extentset" `function(map, locationsOrExtent) { ... }` Fires whenever the **map's** full extent is set, and receives the [Extent](#Extent) or array of [Location](#Location) objects provided to [setExtent](#Map.setExtent) or [extent](#Map.extent). ``` map.addCallback("extentset", function(map, extent) { // convert to an Extent instance if it's a Location array if (extent instanceof Array) { extent = MM.Extent.fromArray(extent); } console.log("map extent:", extent); }); ``` ### "drawn" `function(map) { ... }` Fires whenever the **map** is [redrawn](#Map.draw). ``` map.addCallback("drawn", function(map) { console.log("map drawn!"); }); ``` ## MM.Location Location objects represent [geographic coordinates](http://en.wikipedia.org/wiki/Geographic_coordinate_system) on the Earth's surface, expressed as degrees *latitude* and *longitude*. The constructor takes these two values as its arguments: ``` new MM.Location(latitude, longitude) ``` Locations are most often used when [getting](#Map.getCenter) and [setting](#Map.setCenter) the center of a [map](#Map). You can read the latitude and longitude of a Location by accessing its `lat` and `lon` properties, respectively: ``` var center = map.getCenter(), latitude = center.lat, longitude = center.lon; ``` Note that the [Map](#Map) class doesn't store any references to Location objects internally. Both the [getCenter](#Map.getCenter) and [setCenter](#Map.setCenter) methods convert between geographic and [tile coordinate](#Coordinate) systems, and return copies of objects rather than references. This means that changing the `lat` and `lon` properties of a Location object returned from **getCenter** won't change the map's center; you have to call **setCenter** with the modified Location object. ### lat `location.lat` The location's **latitude**, or distance from the Earth's [equator](http://en.wikipedia.org/wiki/Prime_meridian) in degrees. `-90` is at the bottom of the globe (the south pole in Antarctica), `0` is at the equator, and `90` is at the top (the north pole in the Arctic Ocean). **NOTE:** Because ModestMaps uses a [spherical Mercator projection](http://en.wikipedia.org/wiki/Mercator_projection), points at Earth's extreme north and south poles become infinitely large and impossible to model. This limits the effective range of web maps to `±85º`, depending on zoom level. Setting a map's center to a Location with a latitude outside of this range will most likely have undesired consequences. ### lon `location.lon` The location's **longitude**, or distance from the [prime meridian](http://en.wikipedia.org/wiki/Prime_meridian) in degrees. Positive values are east of the prime meridian (towards Asia); negative values are west (towards North America). `±180` degrees is near the [international date line](http://en.wikipedia.org/wiki/International_Date_Line). Longitude values outside of the `[-180, 180]` range "wrap", so a longitude of `190` degrees may be converted to `-170` in certain calculations. ### Location.fromString `MM.Location.fromString(str)` Parse a string in the format `"lat,lon"` into a new Location object. ### Location.distance `MM.Location.distance(a, b, earthRadius)` Get the physical distance (along a [great circle](http://en.wikipedia.org/wiki/Great_circle)) between locations **a** and **b**, assuming an optional **Earth radius**: * `6378000` meters (the default) * `3963.1` "statute" miles * `3443.9` nautical miles * `6378` kilometers ### Location.interpolate `MM.Location.interpolate(a, b, t)` Interpolate along a [great circle](http://en.wikipedia.org/wiki/Great_circle) between locations **a** and **b** at bias point **t** (a number between 0 and 1). ### Location.bearing `MM.Location.bearing(a, b)` Determine the direction in degrees between locations **a** and **b**. Note that bearing direction is not constant along significant [great cirlce](http://en.wikipedia.org/wiki/Great_circle) arcs. ### A warning about the `location` variable name Because browsers reserve the `window.location` variable for [information](https://developer.mozilla.org/en/DOM/window.location) about the current page location, we suggest using variable names other than `location` to avoid namespace conflicts. `center` and `loc` are good alternatives. ## MM.Extent Extent objects represent rectangular geographic bounding boxes, and are identified by their `north`, `south`, `east` and `west` bounds. North and south bounds are expressed as degrees [latitude](#Location.lat); east and west bounds are degrees [longitude](#Location.lon). The constructor takes two forms: ``` new MM.Extent(north, west, south, east) ``` Create an extent bounded by **north**, **west**, **south** and **east** edges. ``` new MM.Extent(northWest, southEast) ``` Create an extent containing both **northWest** and **southEast** [locations](#Location). ### north `extent.north` The northern edge of the extent. The [constructor](#Extent) compares northern and southern values and selects the higher of the two as its `north`. ### south `extent.south` The southern edge of the extent. The [constructor](#Extent) compares northern and southern values and selects the lower of the two as its `south`. ### east `extent.east` The eastern edge of the extent. The [constructor](#Extent) compares eastern and western values and selects the higher of the two as its `east`. ### west `extent.west` The western edge of the extent. The [constructor](#Extent) compares eastern and western bounds and selects the lower of the two values as its `west`. ### northWest `extent.northWest()` Get the extent's northwest corner as a [Location](#Location). ### northEast `extent.northEast()` Get the extent's northeast corner as a [Location](#Location). ### southEast `extent.southEast()` Get the extent's southeast corner as a [Location](#Location). ### southWest `extent.southWest()` Get the extent's southwest corner as a [Location](#Location). ### center `extent.center()` Get the extent's center as a [Location](#Location). ### containsLocation `extent.containsLocation(lcoation)` Returns `true` if the **location** falls within the **extent**, otherwise `false`. ``` var extent = new MM.Extent(37.8, -122.5, 37.6, -122.3); var sf = new MM.Location(37.764, -122.419); var oakland = new MM.Location(37.804, -122.271); extent.containsLocation(sf); // true extent.containsLocation(oakland); // false ``` ### encloseLocation `extent.encloseLocation(location)` Update the bounds of **extent** to include the provided **location**. ``` var extent = new MM.Extent(37.8, -122.5, 37.6, -122.3); var oakland = new MM.Location(37.804, -122.271); extent.encloseLocation(oakland); extent.containsLocation(oakland); // true ``` ### encloseLocations `extent.encloseLocations(locations)` Update the bounds of **extent** to include the provided **locations** (an array of [Location](#Location) objects). ### encloseExtent `extent.encloseExtent(otherExtent)` Update the bounds of **extent** to include the bounds of the **other extent**. ### setFromLocations `extent.setFromLocations(locations)` Reset the bounds of the **extent** and enclose the provided **locations**. ### copy `extent.copy()` Copy the **extent** and its `north`, `south`, `east` and `west` values. ### toArray `extent.toArray()` Returns a two-element array containing the **extent**'s [northwest](#Extent.northWest) and [southeast](#Extent.southEast) locations. ### Extent.fromString `MM.Extent.fromString(str)` Parse a string in the format `"north,west,south,east"` into a new Extent object. ### Extent.fromArray `MM.Extent.fromArray(locations)` Create a new Extent object from an array of [Location](#Location) objects. ## MM.Point Point objects represent *x* and *y* coordinates on the screen, such as a [map's dimensions](#Map.dimensions) and the position of mouse or touch interactions. ``` new MM.Point(x, y) ``` Create a new Point object with **x** and **y** coordinates. `parseFloat()` is used to convert string values to numbers. The resulting object has `x` and `y` properties. Point objects can be used with [pointLocation](#Map.pointLocation) to determine the [geographic coordinates](#Location) of a point on the screen inside a map. For instance: ``` // define the map's dimensions var size = new MM.Point(640, 480); // create a map without any layers or event handlers var map = new MM.Map("map", [], size, []); // zoom to San Francisco map.setCenterZoom(new MM.Location(37.764, -122.419), 8); // get the geographic coordinates of the bottom right corner var southEast = map.pointLocation(size); ``` And vice-versa, you can use [locationPoint](#Map.locationPoint) to get the screen position of a geographic coordinate: ``` var oakland = new MM.Location(37.804, -122.271); var point = map.locationPoint(oakland); // do something with point.x and point.y ``` ### Point.distance `MM.Point.distance(a, b)` Compute the [Euclidian distance](http://en.wikipedia.org/wiki/Euclidean_distance) between two Point objects. ### Point.interpolate `MM.Point.interpolate(a, b, t)` Compute the point on a straight line between points **a** and **b** at normal distance **t**, a number between `0` and `1`. (If `t == .5` the point will be halfway between **a** and **b**.) ## MM.Coordinate Coordinate objects are used internally by ModestMaps to model the surface of the Earth in [Google's spherical Mercator projection](http://en.wikipedia.org/wiki/Google_Maps#Map_projection), which flattens the globe into a square, or *tile*. Coordinate objects represent points within that tile at different `zoom` levels, with `column` and `row` properties indicating their *x* and *y* positions, respectively. Each round number column and row within a zoom level represents a 256-pixel square image displayed in a ModestMaps [tile layer](#Layer). ``` new Coordinate(row, column, zoom) ``` ### zoom `coordinate.zoom` Coordinates are always expressed relative to a specific **zoom** level. At zoom `0`, the Earth fits into a single square tile, `Coordinate(0, 0, 0)`. With each increase in zoom, every tile is divided into 4 parts, so at zoom level `1` the Earth becomes 4 tiles; at zoom level `2` it becomes 16. Coordinates can be converted to different zoom levels with [zoomTo](#Coordinate.zoomTo). ### column `coordinate.column` A Coordinate's `column` represents a tile's relative *x* position at its zoom level. At zoom `0` there is one column. With each increase in zoom, the number of columns doubles, so at zoom `1` there are two (`0 >= column < 2`), at zoom `2` there are four (`0 >= column < 4`), and so on. ### row `coordinate.row` A Coordinate's `row` represents a tile's relative *y* position at its zoom level. At zoom `0` there is only one tile. With each increase in zoom, the number of rows doubles, so at zoom `1` there are two (`0 >= row < 2`), at zoom `2` there are four (`0 >= row < 4`), and so on. ### copy `coordinate.copy()` Copy the **coordinate**'s `zoom`, `row` and `column` properties into a new **Coordinate** object. ### container `coordinate.container()` Create a Coordinate object that contains **coordinate** by flooring its `zoom`, `column` and `row` properties. This is the actual "tile" coordinate. ### zoomTo `coordinate.zoomTo(zoom)` Copy **coordinate** and adjust its `row` and `column` properties to match the new **zoom** level. ### zoomBy `coordinate.zoomBy(zoomOffset)` Zoom **coordinate** by the the specified **zoom offset** and return a new Coordinate object. ### up `coordinate.up()` Get the Coordinate above **coordinate**. ### right `coordinate.right()` Get the Coordinate to the right of **coordinate**. ### down `coordinate.down()` Get the Coordinate below **coordinate**. ### left `coordinate.left()` Get the Coordinate to the left of **coordinate**. ### toKey `coordinate.toKey()` Generate a string key for the **coordinate**, e.g. `"(1,1,5)"` (`"zoom,row,column"`). ### toString `coordinate.toString()` Format the **coordinate** as a human-readable string, e.g. `"(5,4 @ 1)"` (`("row,column @ zoom")`) ### Pre-projecting Coordinate are also useful for "pre-projecting" [locations](#Location). Because conversions between screen and geographic coordinates are more computationally expensive than conversions between screen and tile coordinates, you may wish to do the [Location to Coordinate](#Map.locationCoordinate) conversion once then do [Coordinate to Point](#Map.coordinatePoint) conversions subsequently. For example: ``` var map = new MM.Map("map", …); var sfLocation = new MM.Location(37.764, -122.419); var sfCoordinate = map.locationCoordinate(sfLocation); // assuming there is an "sf" element, with CSS positon: absolute var marker = map.parent.appendChild(document.getElementById("sf")); map.addCallback("drawn", function() { var point = map.coordinatePoint(sfCoordinate); marker.style.left = point.x + "px"; marker.style.top = point.y + "px"; }); ``` In this example, `map.coordinatePoint(sfCoordinate)` will be much faster than calling `map.locationPoint(sfLocation)` each time the map is redrawn. You probably won't notice the performance gain with one marker, but you certainly will with hundreds. ## MM.TemplatedLayer The **TemplatedLayer** class provides a simple interface for making layers with templated tile URLs. ``` new MM.TemplatedLayer(templateURL [, subdomains]) ``` Create a layer in which each tile image's URL is a variation of the **URL template** and optional list of **subdomains**. You can learn more about templated tile URLs in the [Template](#Template) reference. Here are some examples: ``` var toner = new MM.TemplatedLayer("http://tile.stamen.com/toner/{Z}/{X}/{Y}.png"); var bing = new MM.TemplatedLayer("http://ecn.t0.tiles.virtualearth.net/tiles/r{Q}?" + "g=689&mkt=en-us&lbl=l1&stl=h"); var osm = new MM.TemplatedLayer("http://tile.openstreetmap.org/{Z}/{X}/{Y}.png"); ``` ## MM.Layer Map layers are where map imagery (including but not limited to tiles) gets rendered. The **Layer** class does all of the heavy lifting to determine which tiles are visible at any given moment, loads them if necessary, then adds them to the DOM and positions them on screen. ``` new MM.Layer(provider [, parent]) ``` Create a new map layer that uses a **provider**, optionally specifying a custom **parent** element (or container). A layer's **provider** is an instance of the [MapProvider](#MapProvider) class, which converts [tile coordinates](#Coordinate) into image URLs. This example provides [Placehold.it](http://placehold.it/) URLs that list the tile coordinates: ``` var provider = new MM.MapProvider(function(coord) { return "http://placehold.it/256x256&text=" + [coord.zoom, coord.column, coord.row].join("/"); }); var layer = new MM.Layer(provider); map.addLayer(layer); ``` The [Template](#Template) class simplifies generating image URLs for tile servers with popular template formats. ### getProvider `layer.getProvider()` Get the layer's current provider. ### setProvider `layer.setProvider()` Set the layer's current provider. ### destroy `layer.destroy()` Destroy the layer, removing all of its tiles from memory and removing its **parent** element from the map. ### draw `layer.draw()` Redraw the layer by loading, unloading, adding, removing, and repositioning tile images as appropriate. Changing the provider triggers a **draw**. ### requestRedraw `layer.requestRedraw()` Request a layer redraw in 1 second. This works just like [Map.redraw](#Map.redraw). ## MM.MapProvider Map providers convert [tile coordinates](#Coordinate) to image URLs. If your image tiles come in either [XYZ](#TemplateProvider-XYZ) or [quadkey](#TemplateProvider-Quadkey) formats, you should use the [TemplateProvider](#TemplateProvider) class. ``` new MM.MapProvider(getTileUrl) ``` **MapProvider** is an abstract class, meaning that it is meant to be [extended](#MM.extend). The constructor takes a function as its only argument, which is expected to return an image URL (or `null`) for a given [tile coordinate](#Coordinate). This example emulates the behavior of [Template](#Template) using a static URL template: ``` var provider = new MM.MapProvider(function(coord) { return "http://tile.stamen.com/toner/{Z}/{X}/{Y}.png" .replace("{Z}", coord.zoom) .replace("{X}", coord.column) .replace("{Y}", coord.row); }); // Coordinate(row, col, zoom) provider.getTile(new MM.Coordinate(1, 2, 3)); // returns: "http://tile.stamen.com/toner/3/2/1.png" ``` Map providers expose the following interface to [Layer](#Layer) objects: ### getTile `provider.getTile(tileCoord)` Generates a tile URL or DOM element for the given **tile coordinate**. If the returned value is a string, an `` element is created. Otherwise, if truthy, the return value is assumed to be a DOM element. For instance, you could create a map provider that generates `` elements for each tile by customizing **getTile** like so (note that in this case, your tiles will need to either reside on the same server or be served with the appropriate [CORS headers](http://enable-cors.org/)): ``` var myProvider = new MM.MapProvider(function(coord) { return "path/to/tiles/" + [coord.zoom, coord.column, coord.row].join("/") + ".png"; }); myProvider.getTile = function(coord) { var url = this.getTileUrl(coord); if (url) { var canvas = document.createElement("canvas"); canvas.width = canvas.height = 256; var ctx = canvas.getContext("2d"), img = new Image(); img.onload = function() { canvas.drawImage(img); }; img.src = url; return canvas; } }; ``` NOTE: Because layers cache elements returned by this function, there is no guarantee that **getTile** will be called subsequently after a call to **releaseTile** with the same tile coordinate. If your provider needs to be notified of "re-added" tiles (ones that are generated once, released, then added again from the cache), it should implement [reAddTile](#MapProvider.reAddTile). ### getTileUrl `provider.getTileUrl(tileCoord)` Get the URL of the tile with the provided **coordinate**. In the abstract **MapProvider** class, **getTile** and **getTileUrl** ### releaseTile `provider.releaseTile(tileCoord)` Clean up any resources required to display the **tile coordinate's** corresponding tile image or element. This is probably only necessary if your provider maintains references (such as a list or numeric reference count) to tiles generated by **getTile**. ### reAddTile `provider.reAddTile(tileCoord)` If a provider implements the **reAddTile** method, cached tiles will be passed to this function so that the provider can know about the re-addition of tiles revived from the layer cache. ## MM.Template ModestMap's Template class extends [MapProvider](#MapProvider), and converts [tile coordinates](#Coordinate) to image URLs using a standard template format. These are the same constructor arguments as in [TemplatedLayer](#TemplatedLayer): ``` new MM.Template(urlTemplate [, subdomains]) ``` Create a new templated provider based on the specified **URL template**, and an optional array of **subdomain** replacements. URL templates may contain the following placeholders: ### X, Y, Z `http://example.com/tiles/{Z}/{X}/{Y}.ext` In an **XYZ** template, the `{X}`, `{Y}` and `{Z}` placeholders are replaced with each [tile's](#Coordinate) `column`, `row` and `zoom` properties, respectively. ``` var toner = new MM.Template("http://tile.stamen.com/toner/{Z}/{X}/{Y}.png"); ``` ### Quadkey `http://example.com/tiles/{Q}.ext` In a **quadkey** template, the `{Q}` placeholder is replaced with each [tile's](#Coordinate) [quadkey string](http://msdn.microsoft.com/en-us/library/bb259689.aspx). These are found on Microsoft's [Bing Maps](http://www.bing.com/maps/) (previously VirtualEarth) tile servers: ``` var bingBase = "http://ecn.t0.tiles.virtualearth.net/tiles/r{Q}?"; var bing = new MM.Template(bingBase + "g=689&mkt=en-us&lbl=l1&stl=h"); ``` ### Subdomains `http://{S}.example.com/...` If an array of **subdomains** is passed to the Template constructor, tile URLs will substitute a predictable selection from the array for any `{S}` placeholder (e.g., the first one gets `subdomains[0]`, the second gets `subdomains[1]`, etc.). Many tile servers support loading via multiple subdomains, e.g.: ``` var osm = new MM.Template("http://{S}.tile.openstreetmap.org/{Z}/{X}/{Y}.png", ["a", "b", "c", "d"]); var bing = new MM.Template("http://ecn.t{S}.tiles.virtualearth.net/tiles/r{Q}" + "?g=689&mkt=en-us&lbl=l1&stl=h", [1, 2, 3, 4]); var terrain = new MM.Template("http://{S}.tile.stamen.com/terrain/{Z}/{X}/{Y}.png", "a b c d".split(" ")); ``` **Multiple subdomains may drastically speed up your maps!** Most browsers place limits on the number of URLs that can be loaded simultaneously from each domain. Using two subdomains in effect doubles the number of image tiles that can load at the same time. NOTE: `{S}` placeholders need not *necessarily* refer to subdomains. You could, for instance, vary the entire hostname of each tile, like so: ``` var template = new MM.TemplateProvider("http://{S}/tiles/{Z}/{X}/{Y}.png", ["example.com", "example.org", "example.net"]); ``` ## Package Utilities These are provided as useful shortcuts, often used to provide cross-browser compatibility. ### MM.extend `MM.extend(childClass, parentClass)` Extend the `prototype` of **child class** with previously unspecified methods from the **parent class**'s `prototype`. This is how you extend classes in ModestMaps: ``` var MyLayer = function(provider) { // do something MyLayer-specific // then call the Layer constructor MM.Layer.call(this, provider); }; MyLayer.prototype = { getTile: function(coord) { // do something cool here } }; MM.extend(MyLayer, MM.Layer); ``` ### MM.coerceLayer `MM.coerceLayer(layerish)` Coerce the provided **layerish** string or object into a [Layer](#Layer), according to the following rules: 1. If **layerish** is a string, return a new [TemplatedLayer](#TemplatedLayer) with **layerish** as the URL template. 2. If **layerish** has a `draw` function, assume that it's a [Layer](#Layer) instance and return it. 3. Otherwise, assume that it's a [MapProvider](#MapProvider) and return a new [Layer](#Layer) with it as the constructor argument: `new MM.Layer(layerish)`. ### MM.addEvent `MM.addEvent(element, eventType, listener)` Adds the **listener** to the provided **DOM element** for the given **event type**. In browsers that support [DOM Level 2 events](http://www.w3.org/TR/DOM-Level-2-Events/), this calls `element.addEventListener(eventType, listener, false)`. In older versions of Internet Explorer, this uses `element.attachEvent()`. ``` var link = document.getElementById("null-island"), nullIsland = new MM.Location(0, 0); MM.addEvent(link, "click", function(e) { map.setCenterZoom(nullIsland, 12); return MM.cancelEvent(e); }); ``` You can remove event listeners with [MM.removeEvent](#MM.removeEvent). ### MM.removeEvent `MM.removeEvent(element, eventType, listener)` Removes the **listener** from **element** for the given **event type**. ``` MM.removeEvent(link, "click", onClick); ``` ### MM.cancelEvent `MM.cancelEvent(event)` Does whatever is necessary to cancel the provided DOM event and returns `false`. This is useful for preventing the default behavior or `click` events, e.g.: ``` var zoomIn = document.getElementById("zoom-in"); MM.addEvent(zoomIn, "click", function(e) { map.zoomIn(); return MM.cancelEvent(e); }); ``` ### MM.getStyle `MM.getStyle(element, property)` Get the **element**'s [computed style](https://developer.mozilla.org/en/DOM/window.getComputedStyle) of the named **CSS property**. ``` var bgcolor = MM.getStyle(map.parent, "background-color"); ``` ### MM.moveElement `MM.moveElement(element, point)` Updates the CSS properties of **element** so that it appears at the absolute position **point**. If available, this function uses [CSS transforms](http://www.w3.org/TR/css3-transforms/) (which are often hardware accelerated, and thus faster than traditional `top` and `left` properties). ``` var marker = document.getElementById("marker"), sf = new MM.Location(37.764, -122.419); map.addCallback("drawn", function() { var point = map.locationPoint(sf); MM.moveElement(marker, point); }); ``` (See the section on [pre-projecting](#Coordinate-pre-projecting) for a slightly better way of doing this.) ### MM.getFrame `MM.getFrame(frameCallback)` This is a stand-in for [requestAnimationFrame](https://developer.mozilla.org/en/DOM/window.requestAnimationFrame), a feature of modern browsers that calls the **frame callback** function as often as possible without affecting page repainting. ### MM.transformProperty `MM.transformProperty` The name of the property used by the running browser to provide [CSS transforms](http://www.w3.org/TR/css3-transforms/). ### MM.matrixString `MM.matrixString(point)` Convert **point** into a CSS transform-compatible matrix string. modestmaps-js-3.3.6/doc/css/000077500000000000000000000000001210227260500156435ustar00rootroot00000000000000modestmaps-js-3.3.6/doc/css/main.css000066400000000000000000000073511210227260500173070ustar00rootroot00000000000000 body { font-size: 14px; line-height: 22px; font-family: Helvetica Neue, Helvetica, Arial; background: #f4f4f4 url(docs/images/background.png); } #toc { font-family: "Lucida Grande", "Lucida Sans Unicode", Helvetica, Arial, sans-serif !important; background: #fff; position: fixed; top: 0; left: 0; bottom: 0; width: 200px; overflow-y: auto; overflow-x: hidden; -webkit-overflow-scrolling: touch; padding: 15px 0 30px 30px; border-right: 1px solid #bbb; box-shadow: 0 0 20px #ccc; -webkit-box-shadow: 0 0 20px #ccc; -moz-box-shadow: 0 0 20px #ccc; } #toc h1 { color: black; font-size: inherit; line-height: inherit; font-weight: bold; margin: 0; } #toc .version { margin: 0; } #toc h2 { color: black; font-size: inherit; line-height: inherit; font-weight: bold; margin: 15px 0 0 0; } #toc .version { font-size: 10px; font-weight: normal; } #toc ul { font-size: 11px; line-height: 14px; margin: 5px 0 0 0; padding-left: 16px; list-style-type: disc; font-family: Lucida Grande; } #toc ul li { cursor: pointer; margin: 0 0 3px 0; } #toc ul li a { text-decoration: none; color: black; } #toc ul li a:hover { text-decoration: underline; } #content { position: relative; width: 550px; margin: 40px 0 50px 260px; } div.run { position: absolute; right: 15px; width: 26px; height: 18px; background: url('docs/images/arrows.png') no-repeat -26px 0; } div.run:active { background-position: -51px 0; } p, div.container ul { margin: 0 0 25px 0; width: 550px; } p.warning { font-size: 12px; line-height: 18px; font-style: italic; } div.container ul { list-style: circle; padding-left: 15px; font-size: 13px; line-height: 18px; } div.container ul li { margin-bottom: 10px; } div.container ul.small { font-size: 12px; } a, a:visited { color: #444; } a:active, a:hover { color: #000; } a img { border: 0; } h1, h2, h3, h4, h5, h6 { margin: 20px 0 5px 0; } h2 { font-size: 22px; } b.header { font-size: 18px; line-height: 35px; } span.alias { font-size: 14px; font-style: italic; margin-left: 20px; } table { margin: 15px 0 0; padding: 0; } tr, td { margin: 0; padding: 0; } td { padding: 0px 15px 5px 0; } code, pre, tt { font-family: Monaco, Consolas, "Lucida Console", monospace; font-size: 12px; font-weight: normal; line-height: 18px; font-style: normal; padding: 0px 3px; background: #fff; border: 1px solid #ddd; } pre { font-size: 12px; padding: 2px 0 2px 15px; border: 0; border-left: 4px solid #bbb; margin: 0px 0 25px; } pre code { border: 0; padding: 0; background: none; } @media only screen and (-webkit-min-device-pixel-ratio: 1.5) and (max-width: 640px), only screen and (-o-min-device-pixel-ratio: 3/2) and (max-width: 640px), only screen and (min-device-pixel-ratio: 1.5) and (max-width: 640px) { img { max-width: 290px; } div#sidebar { -webkit-overflow-scrolling: initial; position: relative; width: 90%; height: 120px; left: 0; top: -7px; padding: 10px 0 10px 30px; border: 0; } img#logo { width: auto; height: auto; } div.container { margin: 0; width: 100%; } p, div.container ul { max-width: 98%; overflow-x: scroll; } table { position: relative; } tr:first-child td { padding-bottom: 25px; } td.text { padding: 0; position: absolute; left: 0; top: 48px; } tr:last-child td.text { top: 122px; } pre { overflow: scroll; } } modestmaps-js-3.3.6/doc/end.html000066400000000000000000000000431210227260500165040ustar00rootroot00000000000000
modestmaps-js-3.3.6/doc/index.html000066400000000000000000002016771210227260500170650ustar00rootroot00000000000000 modestmaps.js v3.3.4

modestmaps.js

v3.3.4

Getting the Source

Quick Start

Map

Location

Extent

Point

Coordinate

TemplatedLayer

Layer

Template

MapProvider

modestmaps.js

Modest Maps is a bare-bones geographic map display and interaction library. It was designed and conceived by Tom Carden, tweaked by Michal Migurski, and is maintained and hacked on primarily by Tom MacWright and Shawn Allen.

Getting the Source

You can get modestmaps.js by cloning the github repo or downloading it:

After you've downloaded it, you can include by putting this <script> tag into the <head> of your HTML document:

<script type="text/javascript" src="modestmaps.js"></script>

Quick Start

Making a Map

Making a map is easy. First, you'll need a place for your map in the HTML:

<div id="map"></div>

Then, add a script below:

<script type="text/javascript">
var tiles = new MM.TemplatedLayer("http://tile.stamen.com/toner/{Z}/{X}/{Y}.png");
var size = new MM.Point(640, 480);
var map = new MM.Map("map", tiles, size);
</script>

This will create a map showing Stamen's toner tiles in a 640x480-pixel area. See the docs below for more info on layers and map dimensions.

If you want the size of your map to be determined by CSS, you can style your div like so:

#map {
    height: 500px;
}

Then, leave off the size argument to the constructor:

<script type="text/javascript">
var tiles = new MM.TemplatedLayer("http://tile.stamen.com/toner/{Z}/{X}/{Y}.png");
var map = new MM.Map("map", tiles);
</script>

For more info on resizing, see the Map constructor and autoSize docs.

Moving Around

Once you've got your map initialized, you can move it around the world. Geographic locations in modestmaps.js are modeled with the Location class, which is called with latitude and longitude values:

var oakland = new MM.Location(37.804, -122.271);

Once you've got a location, you can tell the map to center on it by calling setCenter:

// center on Oakland, California
map.setCenter(oakland);
// center on Amsterdam, Netherlands
map.setCenter(new MM.Location(52.3702157, 4.8951679));

You can get the current center of the map with getCenter. You can also modify the visible area by setting its extent, which is defined by two or more locations. Setting a map's extent adjusts its center and zoom level so that the rectangular bounding box surrounding the locations is entirely visible:

var oakland = new MM.Location(37.804, -122.271),
    amsterdam = new MM.Location(52.3702157, 4.8951679);
var extent = new MM.Extent(oakland, amsterdam);
map.setExtent(extent)

You can change the map's zoom level with the zoomIn, zoomOut and zoomTo methods:

map.zoomIn();   // increase zoom by 1
map.zoomOut();  // decrease zoom by 1
map.zoomTo(12); // zoom to level 12

Working with Layers

One popular feature of many online maps is a "hybrid" style, which overlays satellite imagery with graphical labels. You can achieve this effect in modestmaps.js by creating two tile layers, and passing them both to the Map constructor as an array:

var baseURL = "http://tile.stamen.com/",
    watercolor = new MM.TemplatedLayer(baseURL + "watercolor/{Z}/{X}/{Y}.jpg"),
    labels = new MM.TemplatedLayer(baseURL + "toner-labels/{Z}/{X}/{Y}.jpg");
var map = new MM.Map("map", [watercolor, labels]);

Or you can create the map with one layer, then add the overlay later:

var map = new MM.Map("map", watercolor);
map.addLayer(labels);

See the addLayer docs and the TemplatedLayer class reference for more on working with layers.

MM.Map

The Map class is the core of modestmaps.js.

new MM.Map(parent [, layerOrLayers [, dimensions [, eventHandlers]]])

Creates a new map inside the given parent element, containing the specified layers, optionally with the specified dimensions in pixels and custom event handlers. The Map constructor arguments are described in detail below:

parent

The parent element of the map. This is typically a String indicating the ID of the element:

var map = new MM.Map("map", []);

You can also provide an element reference:

var firstDiv = new MM.Map(document.querySelector("div.map"), ...);

Here's a pattern for inserting maps into a series of classed elements, using document.getElementsByClassName:

var elements = document.getElementsByClassName("map"),
    maps = [];
for (var i = 0; i < elements.length; i++) {
  maps[i] = new MM.Map(elements[i], ...);
}

If an element with the provided ID string is not found, an exception is raised.

If parent is not an object or a string, an exception is raised.

layerOrLayers

Either a single layer object or an array of layer objects. Layer objects should either be instances of the Layer class or implement the Layer interface.

Here's an example of a map with two tile layers, from maps.stamen.com:

var baseURL = "http://tile.stamen.com/",
    watercolor = new MM.TemplatedLayer(baseURL + "watercolor/{Z}/{X}/{Y}.png"),
    labels = new MM.TemplatedLayer(baseURL + "toner-labels/{Z}/{X}/{Y}.png");
var map = new MM.Map("map", [watercolor, labels]);

NOTE: Before modestmaps.js v3.1.1, an exception was raised if layerOrLayers was not an object or an array.

dimensions

An optional map size in pixels, expressed as a Point object.

var size = new MM.Point(512, 512);
var map = new MM.Map("map", [], size);
console.log("map size:", map.dimensions.x, "x", map.dimensions.y);

If dimensions is null or undefined, the dimensions are derived from the width and height of the parent element, and the map's autoSize flag is set to true.

Empty <div> elements have no intrinsic height, so if you don't provide dimensions you'll need to provide the map's parent height in CSS (either inline or in a stylesheet).

eventHandlers

An optional array of interaction event handlers, which should implement the EventHandler interface. If no handlers are provided (eventHandlers === undefined), the map is initialized with mouse and touch handlers.

For instance, to create a map without scroll wheel zooming (which is enabled by default), you can provide drag, and double-click handlers:

var map = new MM.Map("map", [], null, [
  new MM.DragHandler(),
  new MM.DoubleClickHandler()
]);

The touch handler provides panning and zooming in touch-enabled browsers:

var map = new MM.Map("map", [], null, [
  new MM.TouchHandler()
]);

To initialize the map without any interaction handlers, specify eventHandlers as null or an empty array ([]).

getCenter map.getCenter()

Get the map's center location.

var center = map.getCenter();
console.log("center latitude:", center.lat, "+ longitude:", center.lon);

setCenter map.setCenter(location)

Set the map's center location.

var center = new MM.Location(37.764, -122.419);
map.setCenter(center);

getZoom map.getZoom()

Get the map's zoom level.

var zoom = map.getZoom();
console.log("zoom level:", zoom);

setZoom map.setZoom(zoom)

Set the map's zoom level.

map.setZoom(17);

setCenterZoom map.setCenterZoom(location, zoom)

Set the map's center location and zoom level.

map.setCenterZoom(new MM.Location(37.764, -122.419), 17);

getExtent map.getExtent()

Get the visible extent (bounds) of the map as an Extent object.

var extent = map.getExtent();
console.log("northwest location:", extent.northWest());
console.log("southeast location:", extent.southEast());

setExtent map.setExtent(extent [, precise])

Modify the center and zoom of the map so that the provided extent is visible. If precise is true, resulting zoom levels may be fractional. (By default, the map's zoom level is rounded down to keep tile images from blurring.)

var extent = new MM.Extent(
  new MM.Location(55.679, 12.584),
  new MM.Location(55.668, 12.607)
);
map.setExtent(extent, true);

NOTE: For historical reasons, setExtent also accepts an array of Location objects, which are converted using Extent.fromArray.

zoomIn map.zoomIn()

Increase the map's zoom level by one.

zoomOut map.zoomOut()

Decrease the map's zoom level by one.

zoomBy map.zoomBy(zoomOffset)

Zoom the map by the provided offset. Positive offsets zoom in; negative offsets zoom out.

// this is the equivalent of calling map.zoomIn() twice:
map.zoomBy(2);

zoomByAbout map.zoomByAbout(zoomOffset, point)

Zoom the map by the provided zoom offset from a point on the screen in pixels. Positive offsets zoom in; negative offsets zoom out. This function is used by DoubleClickHandler to zoom in on the point where the map is double-clicked.

// zoom in on the upper left corner
var point = new MM.Point(0, 0);
map.zoomByAbout(1, point);

panBy map.panBy(x, y)

Pan the map by the specified x and y distance in pixels. Positive values move the map right and down, respectively; negative values move the map left and up.

// pan 500 pixels to the right
map.panBy(500, 0);
// pan 200 pixels up
map.panBy(0, -200);

panLeft map.panLeft()

panRight map.panRight()

panUp map.panUp()

panDown map.panDown()

Pan the map to the left, right, up or down by 100 pixels. To vary the offset distance, use panBy.

getLayers map.getLayers()

Get a copy of the map's layers array.

var layers = map.getLayers();
var base = layers[0];

getLayerAt map.getLayerAt(index)

Get the layer at a specific index. The first layer is at index 0, the second at 1, etc.

var map = new MM.Map(...);
var base = map.getLayerAt(0);
base.parent.id = "base";

addLayer map.addLayer(layer)

Add layer to the map's layer stack]. This triggers a redraw.

var layer = new MM.TemplatedLayer("http://tile.stamen.com/toner-lines/{Z}/{X}/{Y}.png");
map.addLayer(layer);

removeLayer map.removeLayer(layer)

Remove layer from the map's layer stack.

setLayerAt map.setLayerAt(index, newLayer)

Replace the existing layer at index with the new layer.

var layer = new MM.TemplatedLayer("http://tile.stamen.com/toner/{Z}/{X}/{Y}.png");
map.setLayerAt(0, layer);

insertLayerAt map.insertLayerAt(index, layer)

Insert a layer at the provided index.

// let's assume the map has 2 layers already
var layer = new MM.TemplatedLayer("http://tile.stamen.com/toner-lines/{Z}/{X}/{Y}.png");
map.insertLayerAt(1, layer);
// (now it has 3, with our new layer at index 1)

removeLayerAt map.removeLayerAt(index)

Remove the layer at the provided index.

// remove the second layer
map.removeLayerAt(1);

swapLayersAt map.swapLayersAt(indexA, indexB)

Swap the z-index (order) or the layers at indexA and indexB.

// swap the bottom and top layers
var bottom = 0,
    top = map.getLayers().length - 1;
map.swapLayersAt(bottom, top);

pointLocation map.pointLocation(screenPoint)

Convert a point on the screen to a location (a point on the Earth).

pointCoordinate map.pointCoordinate(screenPoint)

Convert a point on the screen to a tile coordinate.

locationPoint locationPoint(location)

Convert a location (a point on the Earth) to a point on the screen.

locationCoordinate map.locationCoordinate(location)

Convert a location (a point on the Earth) to a tile coordinate.

coordinateLocation map.coordinateLocation(coord)

Convert a tile coordinate to a location (a point on the Earth).

coordinatePoint map.coordinatePoint(coord)

Convert a tile coordinate to a point on the screen.

setZoomRange map.setZoomRange(minZoom, maxZoom)

Set the map's minimum and maximum zoom levels. This function modifies the zoom levels of the map's coordLimits.

setSize map.setSize(dimensions)

Set the map's dimensions in pixels. If the map's autoSize flag is true, setting the size manually sets autoSize to false and prevents further automatic resizing.

map.setSize(new MM.Point(640, 480));

NOTE: The map's current size is available in its dimensions property.

addCallback map.addCallback(eventType, callback)

Add a callback function (listener) to the map for a specific event type. Callback functions always receive the map instance as their first argument; additional arguments differ by event type. See the events list for supported types.

function onPanned(map, offset) {
  console.log("panned by", offset[0], offset[1]);
}

map.addCallback("panned", onPanned);

You can remove callbacks with removeCallback.

removeCallback map.removeCallback(eventType, callback)

Remove a callback function (listener) for the given event type. You can add callbacks with addCallback.

map.removeCallback("panned", onPanned);

draw map.draw()

Redraw the map and its layers. First, the map enforces its coordLimits on its center and zoom. If autoSize is true, the map's dimensions are recalculated from its parent. Lastly, each of the map's layers is drawn.

requestRedraw map.requestRedraw()

Request a "lazy" call to draw in 1 second. This is useful if you're responding to lots of user input and know that you'll need to redraw the map eventually, but not immediately.

Multiple calls to requestRedraw within 1 second of one another will be ignored, so this is a perfectly reasonable thing to do:

setInterval(function() {
  map.requestRedraw();
}, 100);

Hybrid Methods

Hybrid methods behave differently depending on whether they receive arguments: The "getter" form (with no arguments) returns the current value, and the "setter" form sets it to the value provided then returns this, which makes function chaining possible (a la jQuery, d3, and Polymaps).

center map.center([location])

Get or set the map's center location.

var center = map.center();
center.lat += .1;
map.center(center);

zoom map.zoom([level])

Get or set the map's zoom level.

var zoom = map.zoom();
zoom -= 3;
map.zoom(zoom);

extent map.extent([locationsOrExtent [, precise]])

Get or set the map's extent. If precise is true, resulting zoom levels may be fractional.

// get the extent, check if it contains a location…
var extent = map.extent(),
    loc = new MM.Location(37.764, -122.419);
if (!extent.containsLocation(loc)) {
  // then enclose the location and set the map's new extent
  extent.encloseLocation(loc);
  map.extent(extent);
}

Map Properties

autoSize map.autoSize

The autoSize property is set to true if no dimensions are provided in the constructor. When autoSize is true, the map's dimensions are recalculated (and the map is redrawn) on window resize.

coordinate map.coordinate

The map's current center coordinate.

coordLimits map.coordLimits

An array specifying the map's coordinate bounds, in which the first element defines the top left (northwest) and outermost zoom level, and the second defines the bottom right (southwest) and innermost zoom.

You can adjust the minimum and maximum zoom levels of the map without affecting the bounds with setZoomRange.

dimensions map.dimensions

The map's current dimensions, expressed as a Point.

// the bottom right screen coordinate is also its southeast point
var southEast = map.pointLocation(map.dimensions);

parent map.parent

The map's parent (container) DOM element.

map.parent.style.backgroundColor = "green";

projection map.projection

The map's projection, also known as Coordinate Reference System (CRS) or Spatial Reference System (SRS).

tileSize map.tileSize

The pixel dimensions of the map's individual image tiles, expressed as a Point. By default, tiles are 256 pixels square.

// you can use the tile size to estimate the number of tiles visible
// at any given moment:
var maxRows = Math.ceil(map.dimensions.x / map.tileSize.x);
var maxCols = Math.ceil(map.dimensions.y / map.tileSize.y);
var maxTiles = maxRows * maxCols;
console.log("max tiles:", maxTiles);

Map Events

Map events are triggered when the map moves (either in response to a direct function call or indirectly, through user interaction with an event handlers) or is drawn. You can start and stop listening for map events with addCallback and removeCallback:

function onDrawn(map) {
  console.log("map drawn!");
}

// After this, the onDrawn will be called whenever the map changes.
map.addCallback("drawn", onZoomed);
// Later, remove the callback.
map.removeCallback("drawn", onZoomed);

"zoomed" function(map, zoomOffset) { ... }

Fires when the map's zoom level changes, usually in response to zoomBy or zoomByAbout. Note that the zoom offset is the difference between the last zoom level and the new zoom level. You can query the map's current zoom level (rather than the offset) with getZoom.

map.addCallback("zoomed", function(map, zoomOffset) {
  console.log("map zoomed by:", zoomOffset);
});

"panned" function(map, panOffset) { ... }

Fires when the map is panned, and receives the pan offset (delta) in pixels as a two-element array ([dx, dy]).

map.addCallback("panned", function(map, panOffset) {
  var dx = panOffset[0],
      dy = panOffset[1];
  console.log("map panned by x:", dx, "y:", dy);
});

"resized" function(map, dimensions) { ... }

Fires when the map is resized, and receives the map's new dimensions as a Point object.

map.addCallback("panned", function(map, dimensions) {
  console.log("map dimensions:", dimensions.x, "y:", dimensions.y);
});

"extentset" function(map, locationsOrExtent) { ... }

Fires whenever the map's full extent is set, and receives the Extent or array of Location objects provided to setExtent or extent.

map.addCallback("extentset", function(map, extent) {
  // convert to an Extent instance if it's a Location array
  if (extent instanceof Array) {
    extent = MM.Extent.fromArray(extent);
  }
  console.log("map extent:", extent);
});

"drawn" function(map) { ... }

Fires whenever the map is redrawn.

map.addCallback("drawn", function(map) {
  console.log("map drawn!");
});

MM.Location

Location objects represent geographic coordinates on the Earth's surface, expressed as degrees latitude and longitude. The constructor takes these two values as its arguments:

new MM.Location(latitude, longitude)

Locations are most often used when getting and setting the center of a map. You can read the latitude and longitude of a Location by accessing its lat and lon properties, respectively:

var center = map.getCenter(),
    latitude = center.lat,
    longitude = center.lon;

Note that the Map class doesn't store any references to Location objects internally. Both the getCenter and setCenter methods convert between geographic and tile coordinate systems, and return copies of objects rather than references. This means that changing the lat and lon properties of a Location object returned from getCenter won't change the map's center; you have to call setCenter with the modified Location object.

lat location.lat

The location's latitude, or distance from the Earth's equator in degrees. -90 is at the bottom of the globe (the south pole in Antarctica), 0 is at the equator, and 90 is at the top (the north pole in the Arctic Ocean).

NOTE: Because ModestMaps uses a spherical Mercator projection, points at Earth's extreme north and south poles become infinitely large and impossible to model. This limits the effective range of web maps to ±85º, depending on zoom level. Setting a map's center to a Location with a latitude outside of this range will most likely have undesired consequences.

lon location.lon

The location's longitude, or distance from the prime meridian in degrees. Positive values are east of the prime meridian (towards Asia); negative values are west (towards North America). ±180 degrees is near the international date line. Longitude values outside of the [-180, 180] range "wrap", so a longitude of 190 degrees may be converted to -170 in certain calculations.

Location.fromString MM.Location.fromString(str)

Parse a string in the format "lat,lon" into a new Location object.

Location.distance MM.Location.distance(a, b, earthRadius)

Get the physical distance (along a great circle) between locations a and b, assuming an optional Earth radius:

Location.interpolate MM.Location.interpolate(a, b, t)

Interpolate along a great circle between locations a and b at bias point t (a number between 0 and 1).

Location.bearing MM.Location.bearing(a, b)

Determine the direction in degrees between locations a and b. Note that bearing direction is not constant along significant great cirlce arcs.

A warning about the location variable name

Because browsers reserve the window.location variable for information about the current page location, we suggest using variable names other than location to avoid namespace conflicts. center and loc are good alternatives.

MM.Extent

Extent objects represent rectangular geographic bounding boxes, and are identified by their north, south, east and west bounds. North and south bounds are expressed as degrees latitude; east and west bounds are degrees longitude. The constructor takes two forms:

new MM.Extent(north, west, south, east)

Create an extent bounded by north, west, south and east edges.

new MM.Extent(northWest, southEast)

Create an extent containing both northWest and southEast locations.

north extent.north

The northern edge of the extent. The constructor compares northern and southern values and selects the higher of the two as its north.

south extent.south

The southern edge of the extent. The constructor compares northern and southern values and selects the lower of the two as its south.

east extent.east

The eastern edge of the extent. The constructor compares eastern and western values and selects the higher of the two as its east.

west extent.west

The western edge of the extent. The constructor compares eastern and western bounds and selects the lower of the two values as its west.

northWest extent.northWest()

Get the extent's northwest corner as a Location.

northEast extent.northEast()

Get the extent's northeast corner as a Location.

southEast extent.southEast()

Get the extent's southeast corner as a Location.

southWest extent.southWest()

Get the extent's southwest corner as a Location.

center extent.center()

Get the extent's center as a Location.

containsLocation extent.containsLocation(lcoation)

Returns true if the location falls within the extent, otherwise false.

var extent = new MM.Extent(37.8, -122.5, 37.6, -122.3);
var sf = new MM.Location(37.764, -122.419);
var oakland = new MM.Location(37.804, -122.271);
extent.containsLocation(sf); // true
extent.containsLocation(oakland); // false

encloseLocation extent.encloseLocation(location)

Update the bounds of extent to include the provided location.

var extent = new MM.Extent(37.8, -122.5, 37.6, -122.3);
var oakland = new MM.Location(37.804, -122.271);
extent.encloseLocation(oakland);
extent.containsLocation(oakland); // true

encloseLocations extent.encloseLocations(locations)

Update the bounds of extent to include the provided locations (an array of Location objects).

encloseExtent extent.encloseExtent(otherExtent)

Update the bounds of extent to include the bounds of the other extent.

setFromLocations extent.setFromLocations(locations)

Reset the bounds of the extent and enclose the provided locations.

copy extent.copy()

Copy the extent and its north, south, east and west values.

toArray extent.toArray()

Returns a two-element array containing the extent's northwest and southeast locations.

Extent.fromString MM.Extent.fromString(str)

Parse a string in the format "north,west,south,east" into a new Extent object.

Extent.fromArray MM.Extent.fromArray(locations)

Create a new Extent object from an array of Location objects.

MM.Point

Point objects represent x and y coordinates on the screen, such as a map's dimensions and the position of mouse or touch interactions.

new MM.Point(x, y)

Create a new Point object with x and y coordinates. parseFloat() is used to convert string values to numbers. The resulting object has x and y properties.

Point objects can be used with pointLocation to determine the geographic coordinates of a point on the screen inside a map. For instance:

// define the map's dimensions
var size = new MM.Point(640, 480);
// create a map without any layers or event handlers
var map = new MM.Map("map", [], size, []);
// zoom to San Francisco
map.setCenterZoom(new MM.Location(37.764, -122.419), 8);
// get the geographic coordinates of the bottom right corner
var southEast = map.pointLocation(size);

And vice-versa, you can use locationPoint to get the screen position of a geographic coordinate:

var oakland = new MM.Location(37.804, -122.271);
var point = map.locationPoint(oakland);
// do something with point.x and point.y

Point.distance MM.Point.distance(a, b)

Compute the Euclidian distance between two Point objects.

Point.interpolate MM.Point.interpolate(a, b, t)

Compute the point on a straight line between points a and b at normal distance t, a number between 0 and 1. (If t == .5 the point will be halfway between a and b.)

MM.Coordinate

Coordinate objects are used internally by ModestMaps to model the surface of the Earth in Google's spherical Mercator projection, which flattens the globe into a square, or tile. Coordinate objects represent points within that tile at different zoom levels, with column and row properties indicating their x and y positions, respectively. Each round number column and row within a zoom level represents a 256-pixel square image displayed in a ModestMaps tile layer.

new Coordinate(row, column, zoom)

zoom coordinate.zoom

Coordinates are always expressed relative to a specific zoom level. At zoom 0, the Earth fits into a single square tile, Coordinate(0, 0, 0). With each increase in zoom, every tile is divided into 4 parts, so at zoom level 1 the Earth becomes 4 tiles; at zoom level 2 it becomes 16. Coordinates can be converted to different zoom levels with zoomTo.

column coordinate.column

A Coordinate's column represents a tile's relative x position at its zoom level. At zoom 0 there is one column. With each increase in zoom, the number of columns doubles, so at zoom 1 there are two (0 >= column < 2), at zoom 2 there are four (0 >= column < 4), and so on.

row coordinate.row

A Coordinate's row represents a tile's relative y position at its zoom level. At zoom 0 there is only one tile. With each increase in zoom, the number of rows doubles, so at zoom 1 there are two (0 >= row < 2), at zoom 2 there are four (0 >= row < 4), and so on.

copy coordinate.copy()

Copy the coordinate's zoom, row and column properties into a new Coordinate object.

container coordinate.container()

Create a Coordinate object that contains coordinate by flooring its zoom, column and row properties. This is the actual "tile" coordinate.

zoomTo coordinate.zoomTo(zoom)

Copy coordinate and adjust its row and column properties to match the new zoom level.

zoomBy coordinate.zoomBy(zoomOffset)

Zoom coordinate by the the specified zoom offset and return a new Coordinate object.

up coordinate.up()

Get the Coordinate above coordinate.

right coordinate.right()

Get the Coordinate to the right of coordinate.

down coordinate.down()

Get the Coordinate below coordinate.

left coordinate.left()

Get the Coordinate to the left of coordinate.

toKey coordinate.toKey()

Generate a string key for the coordinate, e.g. "(1,1,5)" ("zoom,row,column").

toString coordinate.toString()

Format the coordinate as a human-readable string, e.g. "(5,4 @ 1)" (("row,column @ zoom"))

Pre-projecting

Coordinate are also useful for "pre-projecting" locations. Because conversions between screen and geographic coordinates are more computationally expensive than conversions between screen and tile coordinates, you may wish to do the Location to Coordinate conversion once then do Coordinate to Point conversions subsequently. For example:

var map = new MM.Map("map", …);
var sfLocation = new MM.Location(37.764, -122.419);
var sfCoordinate = map.locationCoordinate(sfLocation);
// assuming there is an "sf" element, with CSS positon: absolute
var marker = map.parent.appendChild(document.getElementById("sf"));
map.addCallback("drawn", function() {
    var point = map.coordinatePoint(sfCoordinate);
    marker.style.left = point.x + "px";
    marker.style.top = point.y + "px";
});

In this example, map.coordinatePoint(sfCoordinate) will be much faster than calling map.locationPoint(sfLocation) each time the map is redrawn. You probably won't notice the performance gain with one marker, but you certainly will with hundreds.

MM.TemplatedLayer

The TemplatedLayer class provides a simple interface for making layers with templated tile URLs.

new MM.TemplatedLayer(templateURL [, subdomains])

Create a layer in which each tile image's URL is a variation of the URL template and optional list of subdomains.

You can learn more about templated tile URLs in the Template reference. Here are some examples:

var toner = new MM.TemplatedLayer("http://tile.stamen.com/toner/{Z}/{X}/{Y}.png");
var bing = new MM.TemplatedLayer("http://ecn.t0.tiles.virtualearth.net/tiles/r{Q}?" +
    "g=689&mkt=en-us&lbl=l1&stl=h");
var osm = new MM.TemplatedLayer("http://tile.openstreetmap.org/{Z}/{X}/{Y}.png");

MM.Layer

Map layers are where map imagery (including but not limited to tiles) gets rendered. The Layer class does all of the heavy lifting to determine which tiles are visible at any given moment, loads them if necessary, then adds them to the DOM and positions them on screen.

new MM.Layer(provider [, parent])

Create a new map layer that uses a provider, optionally specifying a custom parent element (or container). A layer's provider is an instance of the MapProvider class, which converts tile coordinates into image URLs. This example provides Placehold.it URLs that list the tile coordinates:

var provider = new MM.MapProvider(function(coord) {
    return "http://placehold.it/256x256&text=" +
        [coord.zoom, coord.column, coord.row].join("/");
});
var layer = new MM.Layer(provider);
map.addLayer(layer);

The Template class simplifies generating image URLs for tile servers with popular template formats.

getProvider layer.getProvider()

Get the layer's current provider.

setProvider layer.setProvider()

Set the layer's current provider.

destroy layer.destroy()

Destroy the layer, removing all of its tiles from memory and removing its parent element from the map.

draw layer.draw()

Redraw the layer by loading, unloading, adding, removing, and repositioning tile images as appropriate. Changing the provider triggers a draw.

requestRedraw layer.requestRedraw()

Request a layer redraw in 1 second. This works just like Map.redraw.

MM.MapProvider

Map providers convert tile coordinates to image URLs. If your image tiles come in either XYZ or quadkey formats, you should use the TemplateProvider class.

new MM.MapProvider(getTileUrl)

MapProvider is an abstract class, meaning that it is meant to be extended. The constructor takes a function as its only argument, which is expected to return an image URL (or null) for a given tile coordinate. This example emulates the behavior of Template using a static URL template:

var provider = new MM.MapProvider(function(coord) {
    return "http://tile.stamen.com/toner/{Z}/{X}/{Y}.png"
        .replace("{Z}", coord.zoom)
        .replace("{X}", coord.column)
        .replace("{Y}", coord.row);
});
// Coordinate(row, col, zoom)
provider.getTile(new MM.Coordinate(1, 2, 3));
// returns: "http://tile.stamen.com/toner/3/2/1.png"

Map providers expose the following interface to Layer objects:

getTile provider.getTile(tileCoord)

Generates a tile URL or DOM element for the given tile coordinate. If the returned value is a string, an <img> element is created. Otherwise, if truthy, the return value is assumed to be a DOM element.

For instance, you could create a map provider that generates <canvas> elements for each tile by customizing getTile like so (note that in this case, your tiles will need to either reside on the same server or be served with the appropriate CORS headers):

var myProvider = new MM.MapProvider(function(coord) {
    return "path/to/tiles/" +
        [coord.zoom, coord.column, coord.row].join("/") +
        ".png";
});
myProvider.getTile = function(coord) {
    var url = this.getTileUrl(coord);
    if (url) {
        var canvas = document.createElement("canvas");
        canvas.width = canvas.height = 256;
        var ctx = canvas.getContext("2d"),
            img = new Image();
        img.onload = function() {
            canvas.drawImage(img);
        };
        img.src = url;
        return canvas;
    }
};

NOTE: Because layers cache elements returned by this function, there is no guarantee that getTile will be called subsequently after a call to releaseTile with the same tile coordinate. If your provider needs to be notified of "re-added" tiles (ones that are generated once, released, then added again from the cache), it should implement reAddTile.

getTileUrl provider.getTileUrl(tileCoord)

Get the URL of the tile with the provided coordinate. In the abstract MapProvider class, getTile and getTileUrl

releaseTile provider.releaseTile(tileCoord)

Clean up any resources required to display the tile coordinate's corresponding tile image or element. This is probably only necessary if your provider maintains references (such as a list or numeric reference count) to tiles generated by getTile.

reAddTile provider.reAddTile(tileCoord)

If a provider implements the reAddTile method, cached tiles will be passed to this function so that the provider can know about the re-addition of tiles revived from the layer cache.

MM.Template

ModestMap's Template class extends MapProvider, and converts tile coordinates to image URLs using a standard template format. These are the same constructor arguments as in TemplatedLayer:

new MM.Template(urlTemplate [, subdomains])

Create a new templated provider based on the specified URL template, and an optional array of subdomain replacements. URL templates may contain the following placeholders:

X, Y, Z http://example.com/tiles/{Z}/{X}/{Y}.ext

In an XYZ template, the {X}, {Y} and {Z} placeholders are replaced with each tile's column, row and zoom properties, respectively.

var toner = new MM.Template("http://tile.stamen.com/toner/{Z}/{X}/{Y}.png");

Quadkey http://example.com/tiles/{Q}.ext

In a quadkey template, the {Q} placeholder is replaced with each tile's quadkey string. These are found on Microsoft's Bing Maps (previously VirtualEarth) tile servers:

var bingBase = "http://ecn.t0.tiles.virtualearth.net/tiles/r{Q}?";
var bing = new MM.Template(bingBase + "g=689&mkt=en-us&lbl=l1&stl=h");

Subdomains http://{S}.example.com/...

If an array of subdomains is passed to the Template constructor, tile URLs will substitute a predictable selection from the array for any {S} placeholder (e.g., the first one gets subdomains[0], the second gets subdomains[1], etc.). Many tile servers support loading via multiple subdomains, e.g.:

var osm = new MM.Template("http://{S}.tile.openstreetmap.org/{Z}/{X}/{Y}.png",
    ["a", "b", "c", "d"]);
var bing = new MM.Template("http://ecn.t{S}.tiles.virtualearth.net/tiles/r{Q}" +
    "?g=689&mkt=en-us&lbl=l1&stl=h",
    [1, 2, 3, 4]);
var terrain = new MM.Template("http://{S}.tile.stamen.com/terrain/{Z}/{X}/{Y}.png",
    "a b c d".split(" "));

Multiple subdomains may drastically speed up your maps! Most browsers place limits on the number of URLs that can be loaded simultaneously from each domain. Using two subdomains in effect doubles the number of image tiles that can load at the same time.

NOTE: {S} placeholders need not necessarily refer to subdomains. You could, for instance, vary the entire hostname of each tile, like so:

var template = new MM.TemplateProvider("http://{S}/tiles/{Z}/{X}/{Y}.png",
    ["example.com", "example.org", "example.net"]);

Package Utilities

These are provided as useful shortcuts, often used to provide cross-browser compatibility.

MM.extend MM.extend(childClass, parentClass)

Extend the prototype of child class with previously unspecified methods from the parent class's prototype. This is how you extend classes in ModestMaps:

var MyLayer = function(provider) {
    // do something MyLayer-specific
    // then call the Layer constructor
    MM.Layer.call(this, provider);
};

MyLayer.prototype = {
    getTile: function(coord) {
        // do something cool here
    }
};

MM.extend(MyLayer, MM.Layer);

MM.coerceLayer MM.coerceLayer(layerish)

Coerce the provided layerish string or object into a Layer, according to the following rules:

  1. If layerish is a string, return a new TemplatedLayer with layerish as the URL template.
  2. If layerish has a draw function, assume that it's a Layer instance and return it.
  3. Otherwise, assume that it's a MapProvider and return a new Layer with it as the constructor argument: new MM.Layer(layerish).

MM.addEvent MM.addEvent(element, eventType, listener)

Adds the listener to the provided DOM element for the given event type. In browsers that support DOM Level 2 events, this calls element.addEventListener(eventType, listener, false). In older versions of Internet Explorer, this uses element.attachEvent().

var link = document.getElementById("null-island"),
    nullIsland = new MM.Location(0, 0);
MM.addEvent(link, "click", function(e) {
    map.setCenterZoom(nullIsland, 12);
    return MM.cancelEvent(e);
});

You can remove event listeners with MM.removeEvent.

MM.removeEvent MM.removeEvent(element, eventType, listener)

Removes the listener from element for the given event type.

MM.removeEvent(link, "click", onClick);

MM.cancelEvent MM.cancelEvent(event)

Does whatever is necessary to cancel the provided DOM event and returns false. This is useful for preventing the default behavior or click events, e.g.:

var zoomIn = document.getElementById("zoom-in");
MM.addEvent(zoomIn, "click", function(e) {
    map.zoomIn();
    return MM.cancelEvent(e);
});

MM.getStyle MM.getStyle(element, property)

Get the element's computed style of the named CSS property.

var bgcolor = MM.getStyle(map.parent, "background-color");

MM.moveElement MM.moveElement(element, point)

Updates the CSS properties of element so that it appears at the absolute position point. If available, this function uses CSS transforms (which are often hardware accelerated, and thus faster than traditional top and left properties).

var marker = document.getElementById("marker"),
    sf = new MM.Location(37.764, -122.419);
map.addCallback("drawn", function() {
    var point = map.locationPoint(sf);
    MM.moveElement(marker, point);
});

(See the section on pre-projecting for a slightly better way of doing this.)

MM.getFrame MM.getFrame(frameCallback)

This is a stand-in for requestAnimationFrame, a feature of modern browsers that calls the frame callback function as often as possible without affecting page repainting.

MM.transformProperty MM.transformProperty

The name of the property used by the running browser to provide CSS transforms.

MM.matrixString MM.matrixString(point)

Convert point into a CSS transform-compatible matrix string.

modestmaps-js-3.3.6/doc/middle.html000066400000000000000000000000751210227260500172010ustar00rootroot00000000000000
modestmaps-js-3.3.6/doc/start.html000066400000000000000000000004661210227260500171040ustar00rootroot00000000000000 modestmaps.js v{VERSION}

modestmaps.js

v{VERSION}

modestmaps-js-3.3.6/doc/toc.md000066400000000000000000000064251210227260500161710ustar00rootroot00000000000000## [Getting the Source](#source) ## [Quick Start](#quick-start) * [Making a map](#quick.map) * [Moving around](#quick.moving) * [Working with layers](#quick.layers) ## [Map](#Map) * [getCenter](#Map.getCenter) * [setCenter](#Map.setCenter) * [getZoom](#Map.getZoom) * [setZoom](#Map.setZoom) * [setCenterZoom](#Map.setCenterZoom) * [getExtent](#Map.getExtent) * [setExtent](#Map.setExtent) * [zoomIn](#Map.zoomIn) * [zoomOut](#Map.zoomOut) * [zoomBy](#Map.zoomBy) * [zoomByAbout](#Map.zoomByAbout) * [panBy](#Map.panBy) * [panLeft](#Map.panLeft) * [panRight](#Map.panRight) * [panUp](#Map.panUp) * [panDown](#Map.panDown) * [center](#Map.center) * [zoom](#Map.zoom) * [extent](#Map.extent) * [addLayer](#Map.addLayer) * [removeLayer](#Map.removeLayer) * [getLayers](#Map.getLayers) * [getLayerAt](#Map.getLayerAt) * [setLayerAt](#Map.setLayerAt) * [insertLayerAt](#Map.insertLayerAt) * [removeLayerAt](#Map.removeLayerAt) * [swapLayersAt](#Map.swapLayersAt) * [addCallback](#Map.addCallback) * [removeCallback](#Map.removeCallback) * [pointLocation](#Map.pointLocation) * [pointCoordinate](#Map.pointCoordinate) * [locationPoint](#Map.locationPoint) * [locationCoordinate](#Map.locationCoordinate) * [coordinateLocation](#Map.coordinateLocation) * [coordinatePoint](#Map.coordinatePoint) * [setZoomRange](#Map.setZoomRange) * [setSize](#Map.setSize) * [autoSize](#Map.autoSize) * [dimensions](#Map.dimensions) * [draw](#Map.draw) * [requestRedraw](#Map.requestRedraw) * [parent](#Map.parent) * [coordinate](#Map.coordinate) * [coordLimits](#Map.coordLimits) * [tileSize](#Map.tileSize) ## [Location](#Location) * [latitude](#Location.lat) and [longitude](#Location.lon) * [Location.fromString](#Location.fromString) * [Location.distance](#Location.fromString) * [Location.interpolate](#Location.fromString) * [Location.bearing](#Location.fromString) ## [Extent](#Extent) * [north](#Extent.north), [east](#Extent.east), [south](#Extent.south) and [west](#Extent.west) * [northWest](#Extent.northWest) * [northEast](#Extent.northEast) * [southEast](#Extent.southEast) * [southWest](#Extent.southWest) * [containsLocation](#Extent.containsLocation) * [encloseLocation](#Extent.encloseLocation) * [encloseLocations](#Extent.encloseLocations) * [encloseExtent](#Extent.encloseExtent) * [setFromLocations](#Extent.setFromLocations) * [copy](#Extent.copy) * [toArray](#Extent.toArray) * [Extent.fromString](#Extent.fromString) * [Extent.fromArray](#Extent.fromArray) ## [Point](#Point) * [Point.distance](#Point.distance) * [Point.interpolate](#Point.interpolate) ## [Coordinate](#Coordinate) * [zoom](#Coordinate.zoom), [column](#Coordinate.column) and [row](#Coordinate.row) * [zoomTo](#Coordinate.zoomTo) * [zoomBy](#Coordinate.zoomBy) * [container](#Coordinate.container) * [up](#Coordinate.up) * [down](#Coordinate.down) * [left](#Coordinate.left) * [right](#Coordinate.right) * [toKey](#Coordinate.toKey) * [toString](#Coordinate.toString) ## [TemplatedLayer](#TemplatedLayer) ## [Layer](#Layer) * [getProvider](#Layer.getProvider) * [setProvider](#Layer.setProvider) * [draw](#Layer.draw) * [requestRedraw](#Layer.requestRedraw) ## [Template](#Template) ## [MapProvider](#MapProvider) * [getTile](#MapProvider.getTile) * [getTileUrl](#MapProvider.getTileUrl) * [releaseTile](#MapProvider.releaseTile) * [reAddTile](#MapProvider.reAddTile) modestmaps-js-3.3.6/examples/000077500000000000000000000000001210227260500161245ustar00rootroot00000000000000modestmaps-js-3.3.6/examples/README000066400000000000000000000011511210227260500170020ustar00rootroot00000000000000Some 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. Plans involve experimentation with the following: * SVG and Canvas drawing * Webkit/Gecko CSS transforms/animations * IE CSS filters. * arbitrary zoom levels * rotations * touch events * ipad/iphone/ipod/android support Contributions are welcome! Please contact us via github (file an issue or message @RandomEtc, @migurski or @tmcw) if you would like to get involved. modestmaps-js-3.3.6/examples/anyscale/000077500000000000000000000000001210227260500177235ustar00rootroot00000000000000modestmaps-js-3.3.6/examples/anyscale/index.html000066400000000000000000000023601210227260500217210ustar00rootroot00000000000000 Modest Maps JS - Any Zoom Demo
modestmaps-js-3.3.6/examples/bubble/000077500000000000000000000000001210227260500173575ustar00rootroot00000000000000modestmaps-js-3.3.6/examples/bubble/README000066400000000000000000000001401210227260500202320ustar00rootroot00000000000000excanvas is from http://code.google.com/p/explorercanvas/ and licensed under Apache License 2.0 modestmaps-js-3.3.6/examples/bubble/excanvas.js000077500000000000000000000567331210227260500215460ustar00rootroot00000000000000// Copyright 2006 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Known Issues: // // * Patterns are not implemented. // * Radial gradient are not implemented. The VML version of these look very // different from the canvas one. // * Clipping paths are not implemented. // * Coordsize. The width and height attribute have higher priority than the // width and height style values which isn't correct. // * Painting mode isn't implemented. // * Canvas width/height should is using content-box by default. IE in // Quirks mode will draw the canvas using border-box. Either change your // doctype to HTML5 // (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype) // or use Box Sizing Behavior from WebFX // (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html) // * Optimize. There is always room for speed improvements. // only add this code if we do not already have a canvas implementation if (!window.CanvasRenderingContext2D) { (function () { // alias some functions to make (compiled) code shorter var m = Math; var mr = m.round; var ms = m.sin; var mc = m.cos; // this is used for sub pixel precision var Z = 10; var Z2 = Z / 2; var G_vmlCanvasManager_ = { init: function (opt_doc) { var doc = opt_doc || document; if (/MSIE/.test(navigator.userAgent) && !window.opera) { var self = this; doc.attachEvent("onreadystatechange", function () { self.init_(doc); }); } }, init_: function (doc) { if (doc.readyState == "complete") { // create xmlns if (!doc.namespaces["g_vml_"]) { doc.namespaces.add("g_vml_", "urn:schemas-microsoft-com:vml"); } // setup default css var ss = doc.createStyleSheet(); ss.cssText = "canvas{display:inline-block;overflow:hidden;" + // default size is 300x150 in Gecko and Opera "text-align:left;width:300px;height:150px}" + "g_vml_\\:*{behavior:url(#default#VML)}"; // find all canvas elements var els = doc.getElementsByTagName("canvas"); for (var i = 0; i < els.length; i++) { if (!els[i].getContext) { this.initElement(els[i]); } } } }, fixElement_: function (el) { // in IE before version 5.5 we would need to add HTML: to the tag name // but we do not care about IE before version 6 var outerHTML = el.outerHTML; var newEl = el.ownerDocument.createElement(outerHTML); // if the tag is still open IE has created the children as siblings and // it has also created a tag with the name "/FOO" if (outerHTML.slice(-2) != "/>") { var tagName = "/" + el.tagName; var ns; // remove content while ((ns = el.nextSibling) && ns.tagName != tagName) { ns.removeNode(); } // remove the incorrect closing tag if (ns) { ns.removeNode(); } } el.parentNode.replaceChild(newEl, el); return newEl; }, /** * Public initializes a canvas element so that it can be used as canvas * element from now on. This is called automatically before the page is * loaded but if you are creating elements using createElement you need to * make sure this is called on the element. * @param {HTMLElement} el The canvas element to initialize. * @return {HTMLElement} the element that was created. */ initElement: function (el) { el = this.fixElement_(el); el.getContext = function () { if (this.context_) { return this.context_; } return this.context_ = new CanvasRenderingContext2D_(this); }; // do not use inline function because that will leak memory el.attachEvent('onpropertychange', onPropertyChange); el.attachEvent('onresize', onResize); var attrs = el.attributes; if (attrs.width && attrs.width.specified) { // TODO: use runtimeStyle and coordsize // el.getContext().setWidth_(attrs.width.nodeValue); el.style.width = attrs.width.nodeValue + "px"; } else { el.width = el.clientWidth; } if (attrs.height && attrs.height.specified) { // TODO: use runtimeStyle and coordsize // el.getContext().setHeight_(attrs.height.nodeValue); el.style.height = attrs.height.nodeValue + "px"; } else { el.height = el.clientHeight; } //el.getContext().setCoordsize_() return el; } }; function onPropertyChange(e) { var el = e.srcElement; switch (e.propertyName) { case 'width': el.style.width = el.attributes.width.nodeValue + "px"; el.getContext().clearRect(); break; case 'height': el.style.height = el.attributes.height.nodeValue + "px"; el.getContext().clearRect(); break; } } function onResize(e) { var el = e.srcElement; if (el.firstChild) { el.firstChild.style.width = el.clientWidth + 'px'; el.firstChild.style.height = el.clientHeight + 'px'; } } G_vmlCanvasManager_.init(); // precompute "00" to "FF" var dec2hex = []; for (var i = 0; i < 16; i++) { for (var j = 0; j < 16; j++) { dec2hex[i * 16 + j] = i.toString(16) + j.toString(16); } } function createMatrixIdentity() { return [ [1, 0, 0], [0, 1, 0], [0, 0, 1] ]; } function matrixMultiply(m1, m2) { var result = createMatrixIdentity(); for (var x = 0; x < 3; x++) { for (var y = 0; y < 3; y++) { var sum = 0; for (var z = 0; z < 3; z++) { sum += m1[x][z] * m2[z][y]; } result[x][y] = sum; } } return result; } function copyState(o1, o2) { o2.fillStyle = o1.fillStyle; o2.lineCap = o1.lineCap; o2.lineJoin = o1.lineJoin; o2.lineWidth = o1.lineWidth; o2.miterLimit = o1.miterLimit; o2.shadowBlur = o1.shadowBlur; o2.shadowColor = o1.shadowColor; o2.shadowOffsetX = o1.shadowOffsetX; o2.shadowOffsetY = o1.shadowOffsetY; o2.strokeStyle = o1.strokeStyle; o2.arcScaleX_ = o1.arcScaleX_; o2.arcScaleY_ = o1.arcScaleY_; } function processStyle(styleString) { var str, alpha = 1; styleString = String(styleString); if (styleString.substring(0, 3) == "rgb") { var start = styleString.indexOf("(", 3); var end = styleString.indexOf(")", start + 1); var guts = styleString.substring(start + 1, end).split(","); str = "#"; for (var i = 0; i < 3; i++) { str += dec2hex[Number(guts[i])]; } if ((guts.length == 4) && (styleString.substr(3, 1) == "a")) { alpha = guts[3]; } } else { str = styleString; } return [str, alpha]; } function processLineCap(lineCap) { switch (lineCap) { case "butt": return "flat"; case "round": return "round"; case "square": default: return "square"; } } /** * This class implements CanvasRenderingContext2D interface as described by * the WHATWG. * @param {HTMLElement} surfaceElement The element that the 2D context should * be associated with */ function CanvasRenderingContext2D_(surfaceElement) { this.m_ = createMatrixIdentity(); this.mStack_ = []; this.aStack_ = []; this.currentPath_ = []; // Canvas context properties this.strokeStyle = "#000"; this.fillStyle = "#000"; this.lineWidth = 1; this.lineJoin = "miter"; this.lineCap = "butt"; this.miterLimit = Z * 1; this.globalAlpha = 1; this.canvas = surfaceElement; var el = surfaceElement.ownerDocument.createElement('div'); el.style.width = surfaceElement.clientWidth + 'px'; el.style.height = surfaceElement.clientHeight + 'px'; el.style.overflow = 'hidden'; el.style.position = 'absolute'; surfaceElement.appendChild(el); this.element_ = el; this.arcScaleX_ = 1; this.arcScaleY_ = 1; }; var contextPrototype = CanvasRenderingContext2D_.prototype; contextPrototype.clearRect = function() { this.element_.innerHTML = ""; this.currentPath_ = []; }; contextPrototype.beginPath = function() { // TODO: Branch current matrix so that save/restore has no effect // as per safari docs. this.currentPath_ = []; }; contextPrototype.moveTo = function(aX, aY) { this.currentPath_.push({type: "moveTo", x: aX, y: aY}); this.currentX_ = aX; this.currentY_ = aY; }; contextPrototype.lineTo = function(aX, aY) { this.currentPath_.push({type: "lineTo", x: aX, y: aY}); this.currentX_ = aX; this.currentY_ = aY; }; contextPrototype.bezierCurveTo = function(aCP1x, aCP1y, aCP2x, aCP2y, aX, aY) { this.currentPath_.push({type: "bezierCurveTo", cp1x: aCP1x, cp1y: aCP1y, cp2x: aCP2x, cp2y: aCP2y, x: aX, y: aY}); this.currentX_ = aX; this.currentY_ = aY; }; contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) { // the following is lifted almost directly from // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes var cp1x = this.currentX_ + 2.0 / 3.0 * (aCPx - this.currentX_); var cp1y = this.currentY_ + 2.0 / 3.0 * (aCPy - this.currentY_); var cp2x = cp1x + (aX - this.currentX_) / 3.0; var cp2y = cp1y + (aY - this.currentY_) / 3.0; this.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, aX, aY); }; contextPrototype.arc = function(aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise) { aRadius *= Z; var arcType = aClockwise ? "at" : "wa"; var xStart = aX + (mc(aStartAngle) * aRadius) - Z2; var yStart = aY + (ms(aStartAngle) * aRadius) - Z2; var xEnd = aX + (mc(aEndAngle) * aRadius) - Z2; var yEnd = aY + (ms(aEndAngle) * aRadius) - Z2; // IE won't render arches drawn counter clockwise if xStart == xEnd. if (xStart == xEnd && !aClockwise) { xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something // that can be represented in binary } this.currentPath_.push({type: arcType, x: aX, y: aY, radius: aRadius, xStart: xStart, yStart: yStart, xEnd: xEnd, yEnd: yEnd}); }; contextPrototype.rect = function(aX, aY, aWidth, aHeight) { this.moveTo(aX, aY); this.lineTo(aX + aWidth, aY); this.lineTo(aX + aWidth, aY + aHeight); this.lineTo(aX, aY + aHeight); this.closePath(); }; contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) { // Will destroy any existing path (same as FF behaviour) this.beginPath(); this.moveTo(aX, aY); this.lineTo(aX + aWidth, aY); this.lineTo(aX + aWidth, aY + aHeight); this.lineTo(aX, aY + aHeight); this.closePath(); this.stroke(); }; contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) { // Will destroy any existing path (same as FF behaviour) this.beginPath(); this.moveTo(aX, aY); this.lineTo(aX + aWidth, aY); this.lineTo(aX + aWidth, aY + aHeight); this.lineTo(aX, aY + aHeight); this.closePath(); this.fill(); }; contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) { var gradient = new CanvasGradient_("gradient"); return gradient; }; contextPrototype.createRadialGradient = function(aX0, aY0, aR0, aX1, aY1, aR1) { var gradient = new CanvasGradient_("gradientradial"); gradient.radius1_ = aR0; gradient.radius2_ = aR1; gradient.focus_.x = aX0; gradient.focus_.y = aY0; return gradient; }; contextPrototype.drawImage = function (image, var_args) { var dx, dy, dw, dh, sx, sy, sw, sh; // to find the original width we overide the width and height var oldRuntimeWidth = image.runtimeStyle.width; var oldRuntimeHeight = image.runtimeStyle.height; image.runtimeStyle.width = 'auto'; image.runtimeStyle.height = 'auto'; // get the original size var w = image.width; var h = image.height; // and remove overides image.runtimeStyle.width = oldRuntimeWidth; image.runtimeStyle.height = oldRuntimeHeight; if (arguments.length == 3) { dx = arguments[1]; dy = arguments[2]; sx = sy = 0; sw = dw = w; sh = dh = h; } else if (arguments.length == 5) { dx = arguments[1]; dy = arguments[2]; dw = arguments[3]; dh = arguments[4]; sx = sy = 0; sw = w; sh = h; } else if (arguments.length == 9) { sx = arguments[1]; sy = arguments[2]; sw = arguments[3]; sh = arguments[4]; dx = arguments[5]; dy = arguments[6]; dw = arguments[7]; dh = arguments[8]; } else { throw "Invalid number of arguments"; } var d = this.getCoords_(dx, dy); var w2 = sw / 2; var h2 = sh / 2; var vmlStr = []; var W = 10; var H = 10; // For some reason that I've now forgotten, using divs didn't work vmlStr.push(' ' , '', ''); this.element_.insertAdjacentHTML("BeforeEnd", vmlStr.join("")); }; contextPrototype.stroke = function(aFill) { var lineStr = []; var lineOpen = false; var a = processStyle(aFill ? this.fillStyle : this.strokeStyle); var color = a[0]; var opacity = a[1] * this.globalAlpha; var W = 10; var H = 10; lineStr.push(' max.x) { max.x = c.x; } if (min.y == null || c.y < min.y) { min.y = c.y; } if (max.y == null || c.y > max.y) { max.y = c.y; } } } lineStr.push(' ">'); if (typeof this.fillStyle == "object") { var focus = {x: "50%", y: "50%"}; var width = (max.x - min.x); var height = (max.y - min.y); var dimension = (width > height) ? width : height; focus.x = mr((this.fillStyle.focus_.x / width) * 100 + 50) + "%"; focus.y = mr((this.fillStyle.focus_.y / height) * 100 + 50) + "%"; var colors = []; // inside radius (%) if (this.fillStyle.type_ == "gradientradial") { var inside = (this.fillStyle.radius1_ / dimension * 100); // percentage that outside radius exceeds inside radius var expansion = (this.fillStyle.radius2_ / dimension * 100) - inside; } else { var inside = 0; var expansion = 100; } var insidecolor = {offset: null, color: null}; var outsidecolor = {offset: null, color: null}; // We need to sort 'colors' by percentage, from 0 > 100 otherwise ie // won't interpret it correctly this.fillStyle.colors_.sort(function (cs1, cs2) { return cs1.offset - cs2.offset; }); for (var i = 0; i < this.fillStyle.colors_.length; i++) { var fs = this.fillStyle.colors_[i]; colors.push( (fs.offset * expansion) + inside, "% ", fs.color, ","); if (fs.offset > insidecolor.offset || insidecolor.offset == null) { insidecolor.offset = fs.offset; insidecolor.color = fs.color; } if (fs.offset < outsidecolor.offset || outsidecolor.offset == null) { outsidecolor.offset = fs.offset; outsidecolor.color = fs.color; } } colors.pop(); lineStr.push(''); } else if (aFill) { lineStr.push(''); } else { lineStr.push( '' ); } lineStr.push(""); this.element_.insertAdjacentHTML("beforeEnd", lineStr.join("")); this.currentPath_ = []; }; contextPrototype.fill = function() { this.stroke(true); } contextPrototype.closePath = function() { this.currentPath_.push({type: "close"}); }; /** * @private */ contextPrototype.getCoords_ = function(aX, aY) { return { x: Z * (aX * this.m_[0][0] + aY * this.m_[1][0] + this.m_[2][0]) - Z2, y: Z * (aX * this.m_[0][1] + aY * this.m_[1][1] + this.m_[2][1]) - Z2 } }; contextPrototype.save = function() { var o = {}; copyState(this, o); this.aStack_.push(o); this.mStack_.push(this.m_); this.m_ = matrixMultiply(createMatrixIdentity(), this.m_); }; contextPrototype.restore = function() { copyState(this.aStack_.pop(), this); this.m_ = this.mStack_.pop(); }; contextPrototype.translate = function(aX, aY) { var m1 = [ [1, 0, 0], [0, 1, 0], [aX, aY, 1] ]; this.m_ = matrixMultiply(m1, this.m_); }; contextPrototype.transform = function(m11, m12, m21, m22, dx, dy) { var m1 = [ [m11, m12, 0], [m21, m22, 0], [ dx, dy, 1] ]; this.m_ = matrixMultiply(m1, this.m_); }; contextPrototype.rotate = function(aRot) { var c = mc(aRot); var s = ms(aRot); var m1 = [ [c, s, 0], [-s, c, 0], [0, 0, 1] ]; this.m_ = matrixMultiply(m1, this.m_); }; contextPrototype.scale = function(aX, aY) { this.arcScaleX_ *= aX; this.arcScaleY_ *= aY; var m1 = [ [aX, 0, 0], [0, aY, 0], [0, 0, 1] ]; this.m_ = matrixMultiply(m1, this.m_); }; /******** STUBS ********/ contextPrototype.clip = function() { // TODO: Implement }; contextPrototype.arcTo = function() { // TODO: Implement }; contextPrototype.createPattern = function() { return new CanvasPattern_; }; // Gradient / Pattern Stubs function CanvasGradient_(aType) { this.type_ = aType; this.radius1_ = 0; this.radius2_ = 0; this.colors_ = []; this.focus_ = {x: 0, y: 0}; } CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) { aColor = processStyle(aColor); this.colors_.push({offset: 1-aOffset, color: aColor}); }; function CanvasPattern_() {} // set up externs G_vmlCanvasManager = G_vmlCanvasManager_; CanvasRenderingContext2D = CanvasRenderingContext2D_; CanvasGradient = CanvasGradient_; CanvasPattern = CanvasPattern_; })(); } // if modestmaps-js-3.3.6/examples/bubble/follower-canvas.js000066400000000000000000000123061210227260500230210ustar00rootroot00000000000000// namespacing! if (!com) { var com = { }; if (!com.modestmaps) { com.modestmaps = { }; } } (function(MM) { MM.Follower = function(map, location, content) { this.coord = map.locationCoordinate(location); this.offset = new MM.Point(0, 0); this.dimensions = new MM.Point(150, 150); this.margin = new MM.Point(10, 10); this.offset = new MM.Point(0, -this.dimensions.y); var follower = this; var callback = function(m, a) { return follower.draw(m); }; map.addCallback('drawn', callback); this.div = document.createElement('div'); this.div.style.position = 'absolute'; this.div.style.width = this.dimensions.x + 'px'; this.div.style.height = this.dimensions.y + 'px'; //this.div.style.backgroundColor = 'white'; //this.div.style.border = 'solid black 1px'; var shadow = document.createElement('canvas'); this.div.appendChild(shadow); if (typeof G_vmlCanvasManager !== 'undefined') shadow = G_vmlCanvasManager.initElement(shadow); shadow.style.position = 'absolute'; shadow.style.left = '0px'; shadow.style.top = '0px'; shadow.width = this.dimensions.x*2; shadow.height = this.dimensions.y; var ctx = shadow.getContext("2d"); ctx.transform(1, 0, -0.5, 0.5, 75, this.dimensions.y/2); ctx.fillStyle = "rgba(0,0,0,0.5)"; this.drawBubblePath(ctx); ctx.fill(); var bubble = document.createElement('canvas'); this.div.appendChild(bubble); if (typeof G_vmlCanvasManager !== 'undefined') bubble = G_vmlCanvasManager.initElement(bubble); bubble.style.position = 'absolute'; bubble.style.left = '0px'; bubble.style.top = '0px'; bubble.width = this.dimensions.x; bubble.height = this.dimensions.y; var bubCtx = bubble.getContext('2d'); bubCtx.strokeStyle = 'black'; bubCtx.fillStyle = 'white'; this.drawBubblePath(bubCtx); bubCtx.fill(); bubCtx.stroke(); var contentDiv = document.createElement('div'); contentDiv.style.position = 'absolute'; contentDiv.style.left = '0px'; contentDiv.style.top = '0px'; contentDiv.style.overflow = 'hidden'; contentDiv.style.width = (this.dimensions.x - this.margin.x) + 'px'; contentDiv.style.height = (this.dimensions.y - this.margin.y - 25) + 'px'; contentDiv.style.padding = this.margin.y + 'px ' + this.margin.x + 'px ' + this.margin.y + 'px ' + this.margin.x + 'px'; contentDiv.innerHTML = content; this.div.appendChild(contentDiv); MM.addEvent(contentDiv, 'mousedown', function(e) { if(!e) e = window.event; return MM.cancelEvent(e); }); map.parent.appendChild(this.div); this.draw(map); } MM.Follower.prototype = { div: null, coord: null, offset: null, dimensions: null, margin: null, draw: function(map) { try { var point = map.coordinatePoint(this.coord); } catch(e) { console.error(e); // too soon? return; } if(point.x + this.dimensions.x + this.offset.x < 0) { // too far left this.div.style.display = 'none'; } else if(point.y + this.dimensions.y + this.offset.y < 0) { // too far up this.div.style.display = 'none'; } else if(point.x + this.offset.x > map.dimensions.x) { // too far right this.div.style.display = 'none'; } else if(point.y + this.offset.y > map.dimensions.y) { // too far down this.div.style.display = 'none'; } else { this.div.style.display = 'block'; MM.moveElement(this.div, { x: Math.round(point.x + this.offset.x), y: Math.round(point.y + this.offset.y), scale: 1, width: this.dimensions.x, height: this.dimensions.y }); } }, drawBubblePath: function(ctx) { ctx.beginPath(); ctx.moveTo(10, this.dimensions.y); ctx.lineTo(35, this.dimensions.y-25); ctx.lineTo(this.dimensions.x-10, this.dimensions.y-25); ctx.quadraticCurveTo(this.dimensions.x, this.dimensions.y-25, this.dimensions.x, this.dimensions.y-35); ctx.lineTo(this.dimensions.x, 10); ctx.quadraticCurveTo(this.dimensions.x, 0, this.dimensions.x-10, 0); ctx.lineTo(10, 0); ctx.quadraticCurveTo(0, 0, 0, 10); ctx.lineTo(0, this.dimensions.y-35); ctx.quadraticCurveTo(0, this.dimensions.y-25, 10, this.dimensions.y-25); ctx.lineTo(15, this.dimensions.y-25); ctx.moveTo(10, this.dimensions.y); } }; })(com.modestmaps) modestmaps-js-3.3.6/examples/bubble/index.html000066400000000000000000000024661210227260500213640ustar00rootroot00000000000000 Modest Maps JS

Modest Maps JS

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

The above div is a map initialized like so:

// "import" the namespace
var mm = com.modestmaps;

var layer = new mm.TemplatedLayer('http://osm-bayarea.s3.amazonaws.com/{Z}-r{Y}-c{X}.jpg');

map = new mm.Map('map', layer, new mm.Point(1024,768))

var f = new mm.Follower(map, new mm.Location(37.811530, -122.2666097), 'Broadway and Grand');

map.setCenterZoom(new mm.Location(37.811530, -122.2666097), 14);

Hands up who wants overlays?

modestmaps-js-3.3.6/examples/cabs/000077500000000000000000000000001210227260500170345ustar00rootroot00000000000000modestmaps-js-3.3.6/examples/cabs/cab-follower.js000066400000000000000000000052371210227260500217550ustar00rootroot00000000000000// namespacing! if (!com) { var com = { }; if (!com.modestmaps) { com.modestmaps = { }; } } com.modestmaps.CabFollower = function(map, location, content) { this.coord = map.locationCoordinate(location); this.dimensions = new com.modestmaps.Point(20, 20); this.offset = new com.modestmaps.Point(this.dimensions.x/2, -this.dimensions.y/2); var follower = this; var callback = function(m, a) { return follower.draw(m); }; map.addCallback('panned', callback); map.addCallback('zoomed', callback); map.addCallback('centered', callback); map.addCallback('extentset', callback); this.div = document.createElement('div'); this.div.style.position = 'absolute'; this.div.style.width = this.dimensions.x + 'px'; this.div.style.height = this.dimensions.y + 'px'; var circle = document.createElement('canvas'); this.div.appendChild(circle); if (typeof G_vmlCanvasManager !== 'undefined') circle = G_vmlCanvasManager.initElement(circle); circle.style.position = 'absolute'; circle.style.left = '0px'; circle.style.top = '0px'; circle.width = this.dimensions.x; circle.height = this.dimensions.y; var ctx = circle.getContext("2d"); ctx.lineWidth = 3; ctx.strokeStyle = "rgba(255,255,0,1)"; ctx.fillStyle = "rgba(0,0,0,1)"; ctx.beginPath(); ctx.arc(this.dimensions.x/2, this.dimensions.x/2, -2+this.dimensions.x/2, 0, Math.PI*2, true); ctx.closePath(); ctx.fill(); ctx.stroke(); map.parent.appendChild(this.div); this.draw(map); } com.modestmaps.CabFollower.prototype = { div: null, coord: null, offset: null, dimensions: null, margin: null, draw: function(map) { try { var point = map.coordinatePoint(this.coord); } catch(e) { // too soon? return; } if(point.x + this.dimensions.x + this.offset.x < 0) { // too far left this.div.style.display = 'none'; } else if(point.y + this.dimensions.y + this.offset.y < 0) { // too far up this.div.style.display = 'none'; } else if(point.x + this.offset.x > map.dimensions.x) { // too far right this.div.style.display = 'none'; } else if(point.y + this.offset.y > map.dimensions.y) { // too far down this.div.style.display = 'none'; } else { this.div.style.display = 'block'; this.div.style.left = point.x + this.offset.x + 'px'; this.div.style.top = point.y + this.offset.y + 'px'; } }, }; modestmaps-js-3.3.6/examples/cabs/index.html000066400000000000000000000052171210227260500210360ustar00rootroot00000000000000 Modest Maps JS

Cabspotting JS

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

modestmaps-js-3.3.6/examples/chaining/000077500000000000000000000000001210227260500177045ustar00rootroot00000000000000modestmaps-js-3.3.6/examples/chaining/index.html000066400000000000000000000011541210227260500217020ustar00rootroot00000000000000 Modest Maps JS - Chaining Wrapper modestmaps-js-3.3.6/examples/cloudmade/000077500000000000000000000000001210227260500200615ustar00rootroot00000000000000modestmaps-js-3.3.6/examples/cloudmade/cloudmade.js000066400000000000000000000013611210227260500223550ustar00rootroot00000000000000// namespacing! if (!MM) { var MM = { }; } MM.CloudMadeProvider = function(key, style) { this.key = key; this.style = style; this.tileWidth = 256; this.tileHeight = 256; }; MM.CloudMadeProvider.prototype = { key: null, style: null, getTile: function(coord) { coord = this.sourceCoordinate(coord); var worldSize = Math.pow(2, coord.zoom); var server = new Array('a.', 'b.', 'c.', '')[parseInt(worldSize * coord.row + coord.column, 10) % 4]; var imgPath = new Array(this.key, this.style, this.tileWidth, coord.zoom, coord.column, coord.row).join('/'); return 'http://' + server + 'tile.cloudmade.com/' + imgPath + '.png'; } }; MM.extend(MM.CloudMadeProvider, MM.MapProvider); modestmaps-js-3.3.6/examples/cloudmade/full.html000066400000000000000000000014271210227260500217150ustar00rootroot00000000000000 Modest Maps JS modestmaps-js-3.3.6/examples/cloudmade/index.html000066400000000000000000000024131210227260500220560ustar00rootroot00000000000000 Modest Maps JS

Modest Maps JS

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

The above div is a map initialized like so:

// please use your own API key, see http://developers.cloudmade.com/ for more details

var provider = new MM.CloudMadeProvider('1a914755a77758e49e19a26e799268b7','998');

map = new MM.Map('map', new MM.Layer(provider), new MM.Point(640,480))

map.setCenterZoom(new MM.Location(37.804656, -122.263606), 14);

Hands up who wants overlays?

modestmaps-js-3.3.6/examples/enforce-limits/000077500000000000000000000000001210227260500210445ustar00rootroot00000000000000modestmaps-js-3.3.6/examples/enforce-limits/index.html000066400000000000000000000022321210227260500230400ustar00rootroot00000000000000 Modest Maps JS modestmaps-js-3.3.6/examples/extent-selector/000077500000000000000000000000001210227260500212515ustar00rootroot00000000000000modestmaps-js-3.3.6/examples/extent-selector/1px.png000066400000000000000000000002111210227260500224610ustar00rootroot00000000000000PNG  IHDR(4tEXtSoftwareAdobe ImageReadyqe<PLTEU~tRNS@f IDATxb`0OmYIENDB`modestmaps-js-3.3.6/examples/extent-selector/index.html000066400000000000000000000160531210227260500232530ustar00rootroot00000000000000 ModestMaps JS: Extent Selector

ModestMaps JS: Extent Selector

Extent (N,W,S,E):

modestmaps-js-3.3.6/examples/extent-selector/modestmaps.extent-selector.js000066400000000000000000000222121210227260500271060ustar00rootroot00000000000000// namespacing! if (!com) { var com = {}; } if (!com.modestmaps) { com.modestmaps = {}; } (function(MM) { MM.ExtentSelector = function(parent, extent) { this.parent = parent || document.createElement("div"); this.initialize(); if (extent) { this.setExtent(extent); } this.callbackManager = new MM.CallbackManager(this, ["extentset", "update", "draw"]); }; MM.ExtentSelector.prototype = { // an MM.Extent instance extent: null, // if this is false, we won't allow moving the extent from the // center allowMoveCenter: true, // event interface callbackManager: null, addCallback: function(event, callback) { this.callbackManager.addCallback(event, callback); }, removeCallback: function(event, callback) { this.callbackManager.removeCallback(event, callback); }, dispatchCallback: function(event, message) { this.callbackManager.dispatchCallback(event, message); }, // extent getter and setter getExtent: function() { return this.extent; }, setExtent: function(extent) { this.extent = this.coerceExtent.apply(this, arguments); this.dispatchCallback("extentset", this.extent.copy()); this.draw(); }, // coerces arguments into a Extent instance coerceExtent: function(extent) { if (extent instanceof Array) { return MM.Extent.fromArray(extent); } else { return extent; } }, // set up UI elements and event dispatchers initialize: function() { this.parent.style.display = "block"; this.parent.style.position = "absolute"; this.parent.style.top = this.parent.style.left = this.parent.style.width = this.parent.style.height = "0px"; this.handles = this.parent.appendChild(document.createElement("div")); this.handles.setAttribute("class", "handles"); this.handles.style.position = "absolute"; this.handles.style.width = this.handles.style.height = "100%"; var names = [ "north", "south", "east", "west", "northwest", "northeast", "southeast", "southwest" ]; for (var i = 0; i < names.length; i++) { this.handles[names[i]] = this.createHandle(names[i]); } this.onHandleDown.bound = MM.bind(this.onHandleDown, this); MM.addEvent(this.handles, "mousedown", this.onHandleDown.bound); }, // clean up event handlers // TODO: remove dynamically created DOM elements (handles, etc.)? destroy: function() { MM.removeEvent(this.handles, "mousedown", this.onHandleDown.bound); }, /* * move the selector by the x and y of the supplied delta (an * MM.Point, presumably), optionally supplying a handle name * ("north", "east", "south", "west", "northeast", "northwest", * "southeast", "southwest"). * * Returns true if the extent changed, false if not. */ moveHandleBy: function(delta, name) { var parent = this.parent, top = parent.offsetTop, left = parent.offsetLeft, bottom = top + parent.offsetHeight, right = left + parent.offsetWidth, width = right - left, height = bottom - top; switch (name) { case "north": top += delta.y; break; case "south": bottom += delta.y; break; case "east": right += delta.x; break; case "west": left += delta.x; break; case "northwest": left += delta.x; top += delta.y; break; case "northeast": right += delta.x; top += delta.y; break; case "southeast": right += delta.x; bottom += delta.y; break; case "southwest": left += delta.x; bottom += delta.y; break; // unrecognized name: move the whole thing default: left += delta.x; right += delta.x; top += delta.y; bottom += delta.y; break; } var x = left; left = Math.min(left, right); right = Math.max(x, right); width = right - left; var y = top; top = Math.min(top, bottom); bottom = Math.max(y, bottom); height = bottom - top; parent.style.left = left + "px"; parent.style.width = (right - left) + "px"; parent.style.top = top + "px"; parent.style.height = (bottom - top) + "px"; return true; }, /* * This is the callback for mousedown events on the handles * element. It moves the specified handle */ onHandleDown: function(e) { var that = this, handles = this.handles, parent = this.parent, target = e.srcElement || e.target, name = target.getAttribute("data-name"), map = this.map, pos = MM.getMousePoint(e, map); // if no handle name is provided and we're not allowed to move // the center, return early (not canceling the event) if (!name && !this.allowMoveCenter) { return; } function mousemove(e) { var p = MM.getMousePoint(e, map), delta = new MM.Point(p.x - pos.x, p.y - pos.y); // console.log("moving", name || "[whole thing]", "by", [delta.x, delta.y]); var changed = that.moveHandleBy(delta, name); if (changed) { that.updateExtent(); } pos = p; return MM.cancelEvent(e); } // listen for mousemove and mouseup events on the entire document var scope = document; function mouseup(e) { MM.removeEvent(scope, "mousemove", mousemove); MM.removeEvent(scope, "mouseup", mouseup); return MM.cancelEvent(e); } MM.addEvent(scope, "mousemove", mousemove); MM.addEvent(scope, "mouseup", mouseup); return MM.cancelEvent(e); }, // create a handle with the given name createHandle: function(name) { var handle = this.handles.appendChild(document.createElement("div")); handle.setAttribute("class", "handle handle-" + name); handle.setAttribute("data-name", name); handle.style.position = "absolute"; return handle; }, // show and hide the selector show: function() { this.parent.style.display = ""; }, hide: function() { this.parent.style.display = "none"; }, // update the screen bounds of the selector from the extent draw: function() { if (this.extent) { var topLeft = this.map.locationPoint(this.extent.northWest()), bottomRight = this.map.locationPoint(this.extent.southEast()), width = bottomRight.x - topLeft.x, height = bottomRight.y - topLeft.y; this.parent.style.top = ~~topLeft.y + "px"; this.parent.style.left = ~~topLeft.x + "px"; this.parent.style.width = ~~width + "px"; this.parent.style.height = ~~height + "px"; this.parent.style.visibility = "visible"; } else { this.parent.style.visibility = "hidden"; } this.dispatchCallback("draw"); }, // update the geographic extent from the screen bounds updateExtent: function() { var top = this.parent.offsetTop, left = this.parent.offsetLeft, width = this.parent.offsetWidth, height = this.parent.offsetHeight; var topLeft = new MM.Point(left, top), bottomRight = new MM.Point(left + width, top + height), northWest = this.map.pointLocation(topLeft), southEast = this.map.pointLocation(bottomRight); this.extent.north = northWest.lat; this.extent.south = southEast.lat; this.extent.west = northWest.lon; this.extent.east = southEast.lon; this.dispatchCallback("extentset", this.extent.copy()); } }; })(com.modestmaps); modestmaps-js-3.3.6/examples/flickr/000077500000000000000000000000001210227260500173765ustar00rootroot00000000000000modestmaps-js-3.3.6/examples/flickr/bluemarble.js000066400000000000000000000007111210227260500220450ustar00rootroot00000000000000MM.BlueMarbleProvider = function() { MM.MapProvider.call(this, function(coordinate) { coordinate = this.sourceCoordinate(coordinate); if (!coordinate) return null; var img = coordinate.zoom.toFixed(0) +'-r'+ coordinate.row.toFixed(0) +'-c'+ coordinate.column.toFixed(0) + '.jpg'; return 'http://s3.amazonaws.com/com.modestmaps.bluemarble/' + img; }); }; com.modestmaps.extend(MM.BlueMarbleProvider, MM.MapProvider); modestmaps-js-3.3.6/examples/flickr/follower.js000066400000000000000000000045171210227260500215740ustar00rootroot00000000000000// namespacing! if (!com) { var com = { }; if (!com.modestmaps) { com.modestmaps = { }; } } (function(MM){ MM.Follower = function(map, location, content, dimensions) { this.coord = map.locationCoordinate(location); this.offset = new MM.Point(0, 0); this.dimensions = dimensions || new MM.Point(100, 50); this.padding = new MM.Point(10, 10); this.offset = new MM.Point(0, -this.dimensions.y); var follower = this; var callback = function(m, a) { return follower.draw(m); }; map.addCallback('drawn', callback); this.div = document.createElement('div'); this.div.style.position = 'absolute'; this.div.style.width = this.dimensions.x + 'px'; this.div.style.height = this.dimensions.y + 'px'; //this.div.style.backgroundColor = 'white'; //this.div.style.border = 'solid black 1px'; this.div.innerHTML = content; MM.addEvent(this.div, 'mousedown', function(e) { if(!e) e = window.event; return MM.cancelEvent(e); }); map.parent.appendChild(this.div); this.draw(map); } MM.Follower.prototype = { div: null, coord: null, offset: null, dimensions: null, padding: null, draw: function(map) { try { var point = map.coordinatePoint(this.coord); } catch(e) { // too soon? return; } if(point.x + this.dimensions.x + this.offset.x < 0) { // too far left this.div.style.display = 'none'; } else if(point.y + this.dimensions.y + this.offset.y < 0) { // too far up this.div.style.display = 'none'; } else if(point.x + this.offset.x > map.dimensions.x) { // too far right this.div.style.display = 'none'; } else if(point.y + this.offset.y > map.dimensions.y) { // too far down this.div.style.display = 'none'; } else { this.div.style.display = 'block'; MM.moveElement(this.div, { x: Math.round(point.x + this.offset.x), y: Math.round(point.y + this.offset.y), scale: 1, width: this.dimensions.x, height: this.dimensions.y }); } } }; })(com.modestmaps); modestmaps-js-3.3.6/examples/flickr/index.html000066400000000000000000000041311210227260500213720ustar00rootroot00000000000000 Modest Maps JS

Modest Maps JS

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

The above div is a map initialized like so:

// "import" the namespace
var MM = com.modestmaps;

var provider = new MM.Layer(new MM.BlueMarbleProvider());

map = new MM.Map('map', provider, new MM.Point(1024,512))

map.setCenterZoom(new MM.Location(20.0, 0), 2);

window.jsonFlickrApi = function(rsp) {
    var photos = rsp.photos.photo;
    for (var i = 0; i < photos.length; i++) {
        var p = photos[i];
        var url = [ 'http://farm', p.farm, '.static.flickr.com/', p.server, '/', p.id, '_', p.secret, '_s.jpg' ].join('');    
        var html = "<" + "img src='" + url + "'" + ">"; // weird for 'eval', sorry
        var location = new MM.Location(p.latitude, p.longitude);
        var dimensions = new MM.Point(75, 75);
        var f = new MM.Follower(map, location, html, dimensions);
    }
}

var script = document.createElement('script');
script.src = 'http://api.flickr.com/services/rest/?'+
               'method=flickr.photos.search&'+
               'api_key=a96cbef093a8c0280d3aed4e0c004d4c&'+
               'tags=clouds&'+
               'extras=geo&'+
               'has_geo=1&'+
               'format=json';
document.getElementsByTagName('head')[0].appendChild(script);

Hands up who wants overlays?

modestmaps-js-3.3.6/examples/geojson/000077500000000000000000000000001210227260500175705ustar00rootroot00000000000000modestmaps-js-3.3.6/examples/geojson/icons/000077500000000000000000000000001210227260500207035ustar00rootroot00000000000000modestmaps-js-3.3.6/examples/geojson/icons/AGGRAVATED_ASSAULT.png000066400000000000000000000012451210227260500242140ustar00rootroot00000000000000PNG  IHDRJL pHYs  gAMA|Q cHRMz%u0`:o_FIDATxĖo@?n ( ,dkɘ,Y;R0c*)$H8;ׇ Iݻ{ϖM%,M8 yLB3(M17h0cP7}& W`m&(@+v2Ru`ׇF TW(B¾ddMQc> l)t媮C@r ;Ț#5lvtS jY5UP\,w]~fTIW*!]R9mkz娠(xQ2;;V0r͓0DDa!KOƶ1vmv*qngSj499ԧz].] ].3 ~}0$ﺙ{Ǡ)N\L|ҿxLuV*ji/OS^;ȇ^׊`{AvUaN[эaWfY1>nrQя5$ J_IENDB`modestmaps-js-3.3.6/examples/geojson/icons/ALCOHOL.png000066400000000000000000000013401210227260500224700ustar00rootroot00000000000000PNG  IHDRJL pHYs  gAMA|Q cHRMz%u0`:o_FVIDATxĖ1Oawb0VsR)Ɓn ueSgtlpT-Z hr+PIn}B& @  S(r@o<`(BB Jρ[ćEAB ʽUSjķαLv^ג;FNPPz ]tbE,P'@4NeX(5z(@["V}Pu£ICk{TUӾzz h (3oNFBw;_ ҁ U .F+G&ɱv/rZq\o&U7Q1i47WbkcXm,'rxw˭<Xlާ98l{5ZhjPHZZMHEUt9v980BZ5`>;`XeiQ>3\P(7.Vi>iƻ3087{Gm7ѭ|vܚD92BZeE[IENDB`modestmaps-js-3.3.6/examples/geojson/icons/ALL.png000066400000000000000000000006741210227260500220300ustar00rootroot00000000000000PNG  IHDRJLtEXtSoftwareAdobe ImageReadyqe<^IDATxڼ1r0EMh4|q\Ⓟv%pCCc* j!A!-7+-zaBcv:}M2WG!YaPDWQ)hafW#,D'H^i[۶Gv3&AnAо8mw@u Z")}`Y0~Oa.R@/|.y kw4(B -\26\ -2 ]ZsZG4n4r/FL |I8g6 2ܓ 9T7[8-c/3'@dX4r!Ҙr]F x]P>uwF݌R5d_=es}`d$3闳Jɠ'8qE ի]+I+!ttbu~C?Kj˽CRwﳹ#i2hh~(إX-̢|gb,hl8.%RO[&C6 Azx틫 4 "ʀ޲lשQbY~~/b(XPefݣra AX=* 3"s|&VL#B0 lxAԀ&p jp ;#ixu_^b@ټ•`ESX< @( a􈷟>\.J,y6|-Ӫ_ۛQ h{O0+?5U,N[ϒVJn]Ǹbuz]Xc7&Xum=wnFaɡx-*'fFzƌi HHrLjv[k|P*2nJ88#9QUf>1^zX _6%LZhX K?EW5^>xv̯on;R;v+g~?@Mz]@u㳞!\Muhތ{n5LV6\|}q>[gIENDB`modestmaps-js-3.3.6/examples/geojson/icons/DISTURBING_THE_PEACE.png000066400000000000000000000014311210227260500244570ustar00rootroot00000000000000PNG  IHDRJL pHYs  gAMA|Q cHRMz%u0`:o_FIDATxĖoq?P HhM_9;EFLV++CcM.:hŗ!mm%%@sps.4ⓐpsyg6+@H.\oҰmmۗҹC`AVr1^{`@$X(t٠mkr1⏠t蕡ʯܐgAb$hprsmBa @ݚMEXF.l/l5,  4)̵όjwQt+ey!r;%;~X`uIB^6 ewߜKRsԵsjcѽiܟ@$eNjl~,Sk,I ~n|O+%Γ)&N CJDSZY-%< FbIu+*O&BHQyJbD@l6O9 Isq5O @v@[@m蘚Ju Tuc['~u CDfך C*ż?mCGhMn5SUؖWkor,򑾜׭Q[y=yTIENDB`modestmaps-js-3.3.6/examples/geojson/icons/MURDER.png000066400000000000000000000012511210227260500224060ustar00rootroot00000000000000PNG  IHDRJL pHYs  gAMA|Q cHRMz%u0`:o_FIDATxĖ?o@8hjeg7(%2emRA<Ӎ+Q-UJ^;%9%eR7P*v9@ۃ4[A-x4{{4:.&O`Ӄ|1 {\N@ h3 ${DxB`(4vP5 9rO-EÑډj&(>9_:ۦ|/( rJΔˏvhUT{ CL´m {:rW׊6j, qa6ek0ZyfY;j}rEd/h>`ZR2DC D&"AEiY̕u'ϕaȀ־sڶUuIJfkVW8ۤ}iχCw77fzm@˺ Ao׳nS'O2} [n&wC=mwң'sgx[^/IENDB`modestmaps-js-3.3.6/examples/geojson/icons/NARCOTICS.png000066400000000000000000000014741210227260500227440ustar00rootroot00000000000000PNG  IHDRJL pHYs  gAMA|Q cHRMz%u0`:o_FIDATxĖOZa|!*hCԤ4PGI.tbm6240 .vC# h1g>9=s^ ¬&8 s Hb<TUof) $#&cwwM"8 BMUֻxDpj gڝ=SK~] xmW"}&V 7[U3ˎ D=IC7֓x@UU̺5ۭrK`l @^aό2Ժэeˌۇ4Atcpڝ6DAcOS?XAMZek?'ْoI`nobIْQ\ mȈlqQU|<qz¨~s5mOXS_7ۮA՜uW&idPj_HҪʠ @2|={v&+5K :TBR+y>PM+哓.xe6KCF]&;Y 'c2Z =ٹ?o|vZML0 vIENDB`modestmaps-js-3.3.6/examples/geojson/icons/ROBBERY.png000066400000000000000000000013621210227260500225170ustar00rootroot00000000000000PNG  IHDRJL pHYs  gAMA|Q cHRMz%u0`:o_FhIDATxĖ?P`B!Z[YKTn8 #[ˌnNzXVrfn[7:P5!JK<ϽKm@ k`  RN8l&`X!2@q L@CB:Xb6 n/XJ <,XPpL&"Ș Mr9p2=)E9]@sͨ/Wr1NJ7'# {2+\#O<#>Blj`8ӾP9ntM;Ǜloөոk6'{3ǹk6YRUhNNK׹m4p)Np*\KFH:MLUi_ dâl ۲ ]tTj S::_{NSU~!9?wb%iO- 2엋sIzLPiLq]hcN*]t2sqݥ+% r٧E^g!Z%SU(7mpLe%}!t|yx: p9p6w'c*v;i;s9g[6G*MIENDB`modestmaps-js-3.3.6/examples/geojson/icons/SIMPLE_ASSAULT.png000066400000000000000000000015471210227260500236050ustar00rootroot00000000000000PNG  IHDRJL pHYs  gAMA|Q cHRMz%u0`:o_FIDATxĖ1LPECME.S9SL0ekDFlhܥh ;؄(*}wYB!P_{\64wBt'*A(-j@& [w&*{sOkP//ݭD%dg-4J$ ޣ-I|D&C?Y* [B=EGxTbx16}U%d ݙ̃Klra)|PYX o5,|dn5PZ^c XwK!-Ddnmuv/ $>xgg'&|+#:-Fu^rH$ÜU*:=Ѩ̃{눆e$i\qˏeqi2L\/ЍPjL\ʊkh1iXVMҫi4=f(g42yZ /^ < Sy12 b{mO5ũٺm"$@˶ijDY BiR `,F}k]?y5,x/a eygfdmCNeꕊOjXsMnx}NӝZK.pt[Ned/Ys/{Qtvd͸O7n=FdIG%KIENDB`modestmaps-js-3.3.6/examples/geojson/icons/THEFT.png000066400000000000000000000012641210227260500222660ustar00rootroot00000000000000PNG  IHDRJL pHYs  gAMA|Q cHRMz%u0`:o_F*IDATxĖ=oP!HEJ=D:5KaP7 T*e$*&R(SAU %"C|P1.Rz6{>~}﹊M"@Q5OI Dg@Z>!p #U k˥Aj:~> m-o30;}`$/k؊h o[20k׮ ى7wb s9@Vaԛ2,kK!)'c}U!]Ϧ MZ8  /%xͯhc[Wu9}%೚# ,P  Ĭ8=NP?Si-t  :Sm l6mڋxs RLRIZiV.*E>|{kZJZ??-] 跗H"`cAҪʠ?H*Ƞ,0NW_ohڎp0 >5DGk@U]̞u)˕Qo 1MMuɨ_Ä9=5yQ?nm"~ LN5IENDB`modestmaps-js-3.3.6/examples/geojson/icons/VANDALISM.png000066400000000000000000000014721210227260500227330ustar00rootroot00000000000000PNG  IHDRJL pHYs  gAMA|Q cHRMz%u0`:o_FIDATxĖOZQ? GL}P0m.!:Վ S7/׹n EMRD v(yA)={OWBЍ(2 ?i"7ZB!Bg[EZ81 !]cprQW-1 %yE^t-Z+M@:VqV"k~$+ xQ"Ձj{ Dѩ Z8*f0J&Ta@:tFն-YU׏=N{ {ǹ7mA~jIlN+9zOZkNߘtZUR- fD/sRSw1}i,QN96[y <+#zokϧzI3cVfI3|A"<5Va["w܋[-VʝjȖ$}N'qLX t Jjd71}yzl'[3{!#n>}^8fZ2RmuP}Lg3܃P^e Z~*e6$) ?%<5ϫsDHdS:ƻv2jF0aTsu(]waIIENDB`modestmaps-js-3.3.6/examples/geojson/icons/VEHICLE_THEFT.png000066400000000000000000000014031210227260500234200ustar00rootroot00000000000000PNG  IHDRJL pHYs  gAMA|Q cHRMz%u0`:o_FyIDATxĖOQ?GkK((ā :t/gX. ӓrGs׼+\/O~{A (Jׄd>,@s(^!@Bt%`x(9C/z%ٗ@W*CQ-װ] Q˺^g_90#4֊wruR^x$; EѦ{l5:5gPsO@peآJBUw ЦQj@jp `81v3#iVuh܋'ypw|%h>+O3+g;B >E ~q,>h 4`48d(HsfWYyD4&yU _^hi8''e< j8uJS:t,L`.d"c4$Tk:EpB~7 ӱXP:m9k McTx$Ff*ͦw%I ABXx%$ *xN ٩l0e 2h@xv3+u.3ۢbdzR* ST6B|Wy\vVuɨxU05;.O3:z9g׭Apƅ/IENDB`modestmaps-js-3.3.6/examples/geojson/index.html000066400000000000000000000073501210227260500215720ustar00rootroot00000000000000 ModestMaps JS: GeoJSON Markers

ModestMaps JS: GeoJSON Markers

modestmaps-js-3.3.6/examples/geojson/modestmaps.markers.js000066400000000000000000000214231210227260500237470ustar00rootroot00000000000000// namespacing! if (!com) { var com = {}; } if (!com.modestmaps) { com.modestmaps = {}; } (function(MM) { /** * The MarkerLayer doesn't do any tile stuff, so it doesn't need to * inherit from MM.Layer. The constructor takes only an optional parent * element. * * Usage: * * // create the map with some constructor parameters * var map = new MM.Map(...); * // create a new MarkerLayer instance and add it to the map * var layer = new MM.MarkerLayer(); * map.addLayer(layer); * // create a marker element * var marker = document.createElement("a"); * marker.innerHTML = "Stamen"; * // add it to the layer at the specified geographic location * layer.addMarker(marker, new MM.Location(37.764, -122.419)); * // center the map on the marker's location * map.setCenterZoom(marker.location, 13); * */ MM.MarkerLayer = function(parent) { this.parent = parent || document.createElement('div'); this.parent.style.cssText = 'position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; margin: 0; padding: 0; z-index: 0'; this.markers = []; this.resetPosition(); }; MM.MarkerLayer.prototype = { // a list of our markers markers: null, // the absolute position of the parent element position: null, // the last coordinate we saw on the map lastCoord: null, draw: function() { // these are our previous and next map center coordinates var prev = this.lastCoord, next = this.map.pointCoordinate({x: 0, y: 0}); // if we've recorded the map's previous center... if (prev) { // if the zoom hasn't changed, find the delta in screen // coordinates and pan the parent element if (prev.zoom == next.zoom) { var p1 = this.map.coordinatePoint(prev), p2 = this.map.coordinatePoint(next), dx = p1.x - p2.x, dy = p1.y - p2.y; // console.log("panned:", [dx, dy]); this.onPanned(dx, dy); // otherwise, reposition all the markers } else { this.onZoomed(); } // otherwise, reposition all the markers } else { this.onZoomed(); } // remember the previous center this.lastCoord = next.copy(); }, // when zoomed, reset the position and reposition all markers onZoomed: function() { this.resetPosition(); this.repositionAllMarkers(); }, // when panned, offset the position by the provided screen coordinate x // and y values onPanned: function(dx, dy) { this.position.x += dx; this.position.y += dy; this.parent.style.left = ~~(this.position.x + .5) + "px"; this.parent.style.top = ~~(this.position.y + .5) + "px"; }, // remove all markers removeAllMarkers: function() { while (this.markers.length > 0) { this.removeMarker(this.markers[0]); } }, /** * Coerce the provided value into a Location instance. The following * values are supported: * * 1. MM.Location instances * 2. Object literals with numeric "lat" and "lon" properties * 3. A string in the form "lat,lon" * 4. GeoJSON objects with "Point" geometries * * This function throws an exception on error. */ coerceLocation: function(feature) { switch (typeof feature) { case "string": // "lat,lon" string return MM.Location.fromString(feature); case "object": // GeoJSON if (typeof feature.geometry === "object") { var geom = feature.geometry; switch (geom.type) { // Point geometries => MM.Location case "Point": // coerce the lat and lon values, just in case var lon = Number(geom.coordinates[0]), lat = Number(geom.coordinates[1]); return new MM.Location(lat, lon); } throw 'Unable to get the location of GeoJSON "' + geom.type + '" geometry!'; } else if (feature instanceof MM.Location || (typeof feature.lat !== "undefined" && typeof feature.lon !== "undefined")) { return feature; } else { throw 'Unknown location object; no "lat" and "lon" properties found!'; } break; case "undefined": throw 'Location "undefined"'; } }, /** * Add an HTML element as a marker, located at the position of the * provided GeoJSON feature, Location instance (or {lat,lon} object * literal), or "lat,lon" string. */ addMarker: function(marker, feature) { if (!marker || !feature) { return null; } // convert the feature to a Location instance marker.location = this.coerceLocation(feature); // remember the tile coordinate so we don't have to reproject every time marker.coord = this.map.locationCoordinate(marker.location); // position: absolute marker.style.position = "absolute"; // update the marker's position this.repositionMarker(marker); // append it to the DOM this.parent.appendChild(marker); // add it to the list this.markers.push(marker); return marker; }, /** * Remove the element marker from the layer and the DOM. */ removeMarker: function(marker) { var index = this.markers.indexOf(marker); if (index > -1) { this.markers.splice(index, 1); } if (marker.parentNode == this.parent) { this.parent.removeChild(marker); } return marker; }, // reset the absolute position of the layer's parent element resetPosition: function() { this.position = new MM.Point(0, 0); this.parent.style.left = this.parent.style.top = "0px"; }, // reposition a single marker element repositionMarker: function(marker) { if (marker.coord) { var pos = this.map.coordinatePoint(marker.coord); // offset by the layer parent position if x or y is non-zero if (this.position.x || this.position.y) { pos.x -= this.position.x; pos.y -= this.position.y; } marker.style.left = ~~(pos.x + .5) + "px"; marker.style.top = ~~(pos.y + .5) + "px"; } else { // TODO: throw an error? } }, // Reposition al markers repositionAllMarkers: function() { var len = this.markers.length; for (var i = 0; i < len; i++) { this.repositionMarker(this.markers[i]); } } }; // Array.indexOf polyfill courtesy of Mozilla MDN: // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf if (!Array.prototype.indexOf) { Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) { "use strict"; if (this === void 0 || this === null) { throw new TypeError(); } var t = Object(this); var len = t.length >>> 0; if (len === 0) { return -1; } var n = 0; if (arguments.length > 0) { n = Number(arguments[1]); if (n !== n) { // shortcut for verifying if it's NaN n = 0; } else if (n !== 0 && n !== Infinity && n !== -Infinity) { n = (n > 0 || -1) * Math.floor(Math.abs(n)); } } if (n >= len) { return -1; } var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0); for (; k < len; k++) { if (k in t && t[k] === searchElement) { return k; } } return -1; } } })(com.modestmaps); modestmaps-js-3.3.6/examples/getbbox/000077500000000000000000000000001210227260500175565ustar00rootroot00000000000000modestmaps-js-3.3.6/examples/getbbox/index.html000066400000000000000000000146331210227260500215620ustar00rootroot00000000000000 Get Bounding Box

Get Bounding Box

Shift-click and drag to draw a bounding box.
 

Click and drag to pan, mouse-wheel to zoom. Shift-click and drag to draw a bounding box.

Powered by Modest Maps JS. Map imagery CC-BY-SA OpenStreetMap.org contributors, used with reference to the OSM tile usage policy.

modestmaps-js-3.3.6/examples/greatcircle/000077500000000000000000000000001210227260500204105ustar00rootroot00000000000000modestmaps-js-3.3.6/examples/greatcircle/index.html000066400000000000000000000033741210227260500224140ustar00rootroot00000000000000 Modest Maps JS - Great Circle Test modestmaps-js-3.3.6/examples/hash/000077500000000000000000000000001210227260500170475ustar00rootroot00000000000000modestmaps-js-3.3.6/examples/hash/index.html000066400000000000000000000023671210227260500210540ustar00rootroot00000000000000 ModestMaps JS: Hash URLs

ModestMaps JS: Hash URLs

Go to: Stamen, null island

modestmaps-js-3.3.6/examples/hello-oakland/000077500000000000000000000000001210227260500206365ustar00rootroot00000000000000modestmaps-js-3.3.6/examples/hello-oakland/index.html000066400000000000000000000070071210227260500226370ustar00rootroot00000000000000 Modest Maps JS - Hello Oakland
modestmaps-js-3.3.6/examples/keyboard/000077500000000000000000000000001210227260500177245ustar00rootroot00000000000000modestmaps-js-3.3.6/examples/keyboard/index.html000066400000000000000000000202261210227260500217230ustar00rootroot00000000000000 Modest Maps JS - Throwable Test

Click/drag to throw the map, or press and hold the arrow keys to fly around.

drag :
zoom :

Built with Modest Maps JS. Tiles Courtesy of MapQuest

modestmaps-js-3.3.6/examples/layers/000077500000000000000000000000001210227260500174235ustar00rootroot00000000000000modestmaps-js-3.3.6/examples/layers/index.html000066400000000000000000000043321210227260500214220ustar00rootroot00000000000000 Modest Maps JS - Layer Sample

modestmaps-js-3.3.6/examples/markerclip/000077500000000000000000000000001210227260500202555ustar00rootroot00000000000000modestmaps-js-3.3.6/examples/markerclip/index.html000066400000000000000000000073061210227260500222600ustar00rootroot00000000000000 Modest Maps JS Marker Demo

Modest Maps JS Marker Demo

modestmaps-js-3.3.6/examples/microsoft/000077500000000000000000000000001210227260500201315ustar00rootroot00000000000000modestmaps-js-3.3.6/examples/microsoft/bing.js000066400000000000000000000044431210227260500214130ustar00rootroot00000000000000// namespacing! if (!MM) { MM = { }; } MM.BingProvider = function(key, style, onready) { this.key = key; this.style = style; // hit the imagery metadata service // http://msdn.microsoft.com/en-us/library/ff701716.aspx // Aerial, AerialWithLabels, Road var script = document.createElement('script'); script.type = 'text/javascript'; document.getElementsByTagName('head')[0].appendChild(script); script.src = 'http://dev.virtualearth.net/REST/V1/Imagery/Metadata/'+style+'/?key='+key+'&jsonp=onBingComplete'; function toMicrosoft(column, row, zoom) { // generate zoom string by interleaving row/col bits // NB:- this assumes you're only asking for positive row/cols var quadKey = ""; for (var i = 1; i <= zoom; i++) { var rowBit = (row >> zoom-i) & 1; var colBit = (column >> zoom-i) & 1; quadKey += (rowBit << 1) + colBit; } return quadKey; } var provider = this; window.onBingComplete = function(data) { var resourceSets = data.resourceSets; for (var i = 0; i < resourceSets.length; i++) { var resources = data.resourceSets[i].resources; for (var j = 0; j < resources.length; j++) { var resource = resources[j]; var serverSalt = Math.floor(Math.random() * 4); provider.getTile = function(coord) { var quadKey = toMicrosoft(coord.column, coord.row, coord.zoom); // this is so that requests will be consistent in this session, rather than totally random var server = Math.abs(serverSalt + coord.column + coord.row + coord.zoom) % 4; return resource.imageUrl .replace('{quadkey}',quadKey) .replace('{subdomain}', resource.imageUrlSubdomains[server]); }; // TODO: use resource.imageWidth // TODO: use resource.imageHeight } } // TODO: display data.brandLogoUri // TODO: display data.copyright onready(provider); }; }; MM.BingProvider.prototype = { key: null, style: null, subdomains: null, getTileUrl: null }; MM.extend(MM.BingProvider, MM.MapProvider); modestmaps-js-3.3.6/examples/microsoft/index.html000066400000000000000000000015061210227260500221300ustar00rootroot00000000000000 Modest Maps JS modestmaps-js-3.3.6/examples/navwindow/000077500000000000000000000000001210227260500201405ustar00rootroot00000000000000modestmaps-js-3.3.6/examples/navwindow/index.html000066400000000000000000000033551210227260500221430ustar00rootroot00000000000000 Modest Maps JS - Nav Window Test
modestmaps-js-3.3.6/examples/node/000077500000000000000000000000001210227260500170515ustar00rootroot00000000000000modestmaps-js-3.3.6/examples/node/.gitignore000066400000000000000000000000101210227260500210300ustar00rootroot00000000000000map.png modestmaps-js-3.3.6/examples/node/README000066400000000000000000000006471210227260500177400ustar00rootroot00000000000000This example works with node.js and (ideally) npm. The included package.json file means you should be able to type `npm install` in this folder to pull down all dependencies. Then do `node modestmaps-static.js` to run the example server. Example URL, San Francisco at zoom level 11 on OpenStreetMap, in an 800x600 image: http://localhost:3000/map?provider=osm&width=800&height=600&zoom=11&lat=37.774929&lon=-122.419415 modestmaps-js-3.3.6/examples/node/modestmaps-static.js000066400000000000000000000062471210227260500230610ustar00rootroot00000000000000var MM = require('../../modestmaps.js'), Canvas = require('canvas'), Image = Canvas.Image; get = require('get'), express = require('express'), fs = require('fs'); function renderStaticMap(provider, dimensions, zoom, location, callback) { var canvas = new Canvas(dimensions.x, dimensions.y), ctx = canvas.getContext('2d'), // default to Google-y Mercator style maps projection = new MM.MercatorProjection(0, MM.deriveTransformation(-Math.PI, Math.PI, 0, 0, Math.PI, Math.PI, 1, 0, -Math.PI, -Math.PI, 0, 1)), tileSize = new MM.Point(256, 256); var centerCoordinate = projection.locationCoordinate(location).zoomTo(zoom); function pointCoordinate(point) { // new point coordinate reflecting distance from map center, in tile widths var coord = centerCoordinate.copy(); coord.column += (point.x - dimensions.x/2) / tileSize.x; coord.row += (point.y - dimensions.y/2) / tileSize.y; return coord; } function coordinatePoint(coord) { // Return an x, y point on the map image for a given coordinate. if (coord.zoom != zoom) { coord = coord.zoomTo(zoom); } var point = new MM.Point(dimensions.x/2, dimensions.y/2); point.x += tileSize.x * (coord.column - centerCoordinate.column); point.y += tileSize.y * (coord.row - centerCoordinate.row); return point; } var startCoord = pointCoordinate(new MM.Point(0,0)).container(), endCoord = pointCoordinate(dimensions).container(); var numRequests = 0, completeRequests = 0; function checkDone() { if (completeRequests == numRequests) { callback(null, canvas); } } function getTile(url, p) { new get(url).asBuffer(function(error,data) { if (error) { callback(url + ' error: ' + error); } else { var img = new Image(); img.src = data; ctx.drawImage(img, p.x, p.y, tileSize.x, tileSize.y); completeRequests++; checkDone(); } }); } for (var column = startCoord.column; column <= endCoord.column; column++) { for (var row = startCoord.row; row <= endCoord.row; row++) { var c = new MM.Coordinate(row, column, zoom), url = provider.getTileUrl(c), p = coordinatePoint(c); if (url) { getTile(url, p); numRequests++; } } } } // just one for now... var providers = { osm: new MM.Template("http://tile.openstreetmap.org/{Z}/{X}/{Y}.png") }; var app = express.createServer(); app.get('/map', function(req,res) { var provider = providers[req.param("provider", "osm")], // default osm width = req.param("width", 800), height = req.param("height", 600), dimensions = new MM.Point(width, height), zoom = parseInt(req.param("zoom", 1), 10), lat = req.param("lat", 0.0), lon = req.param("lon", 0.0), location = new MM.Location(lat, lon); renderStaticMap(provider, dimensions, zoom, location, function(err,canvas) { if (err) { res.send(new Error(err)); } else { res.header('Content-Type', 'image/png'); res.send(canvas.toBuffer()); } }); }); app.listen(3000); modestmaps-js-3.3.6/examples/node/package.json000066400000000000000000000010551210227260500213400ustar00rootroot00000000000000{ "author": "Tom Carden (http://www.tom-carden.co.uk)", "name": "modestmaps-static", "description": "create a static image composed of several map tiles", "version": "0.0.0", "homepage": "https://github.com/stamen/modestmaps-js/", "repository": { "type": "git", "url": "git://github.com/stamen/modestmaps-js.git" }, "main": "modestmaps-static.js", "engines": { "node": "~v0.4.2" }, "dependencies": { "express": "~2.4.3", "canvas": "~0.7.0", "get": "~0.4.0" }, "devDependencies": {} } modestmaps-js-3.3.6/examples/placeholder/000077500000000000000000000000001210227260500204065ustar00rootroot00000000000000modestmaps-js-3.3.6/examples/placeholder/index.html000066400000000000000000000022741210227260500224100ustar00rootroot00000000000000 Modest Maps JS: Placeholder tiles

Modest Maps JS: Placeholder tiles

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

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

modestmaps-js-3.3.6/examples/polar/000077500000000000000000000000001210227260500172415ustar00rootroot00000000000000modestmaps-js-3.3.6/examples/polar/polar.js000066400000000000000000000107051210227260500207170ustar00rootroot00000000000000// THe following values were obtained http://nsidc.org/data/atlas/epsg_3411.html // North var la0=-45; // Central var phi1=90; // Data from http://nsidc.org/data/atlas/epsg_3411.html var phic=70; // optional var a=6378273.0; // Hughes ellipsoid required here http://nsidc.org/data/polar_stereo/ps_grids.html var e=0.081816153; var k0=1; // Scale factor function unproject(x, y) { var degRad=Math.PI / 180; var radDeg=180 / Math.PI; // Equation 14-15 var m1=Math.cos(phi1 * degRad) / Math.pow((1 - Math.pow(e, 2) * Math.pow(Math.sin(phi1 * degRad), 2)), 1 / 2); // Equation 3-1 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; // Equations 21-18 / 38 var p=Math.pow(Math.pow(x, 2) + Math.pow(y, 2), 1 / 2); var ce=2 * Math.atan(p * Math.cos(X1) / (2 * a * k0 * m1)); //Equation 21-37 var X=Math.asin(Math.cos(ce) * Math.sin(X1) + (y * Math.sin(ce) * Math.cos(X1) / p)); // Equation 3-4 // Using X as the first trial for phi 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; // Using this new trial value for phi, not for X 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; 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; // Equation 21-36 // The next trial calculation produces the same phi to seven decimals. Therefore, this is phi. var la= la0 + Math.atan(x * Math.sin(ce) / (p * Math.cos(X1) * Math.cos(ce) - y * Math.sin(X1) * Math.sin(ce))) * radDeg; // If the denominator of the arctan argument is negative (reversed in Snyder), it is necessary to add or subtract 180, // whicever will place final la between +180 and -180. var arctanDenominator = (p * Math.cos(X1) * Math.cos(ce) - y * Math.sin(X1) * Math.sin(ce)); //Reverse coordinates if(arctanDenominator < 0) { if (la < 0) la += 180; else if(la > 0) la -= 180; } return { x: la, y: phi }; } // la = lon; phi = lat function project(phi, la) { var degRad=Math.PI / 180; var radDeg=180 / Math.PI // Equation 3-1 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; 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; // Equation 14-15 var m1=Math.cos(phi1 * degRad) / Math.pow((1 - Math.pow(e, 2) * Math.pow(Math.sin(phi1 * degRad), 2)), 1 / 2); var m=Math.cos(phi * degRad) / Math.pow((1 - Math.pow(e, 2) * Math.pow(Math.sin(phi * degRad), 2)), 1 / 2); if (phi1 == 90) { // Equation 15-9 var t = Math.tan((45-phi/2)*degRad) / Math.pow( (1-e*Math.sin(phi*degRad))/(1+e*Math.sin(phi*degRad)), e/2); var p; if (phic) { var tc = Math.tan((45-phic/2)*degRad) / Math.pow( (1-e*Math.sin(phic*degRad))/(1+e*Math.sin(phic*degRad)), e/2); var mc = Math.cos(phic * degRad) / Math.pow((1 - Math.pow(e, 2) * Math.pow(Math.sin(phic * degRad), 2)), 1 / 2); p = a*mc*t/tc; } else { // Equation 21-33 p = 2 * a * k0 * t / Math.pow( Math.pow(1+e,1+e)*Math.pow(1-e,1-e), 1/2 ); } // Equations 21-30 / 31 / 32 var x = p * Math.sin((la - la0) * degRad); var y = -p * Math.cos((la - la0) * degRad); var k = p / (a * m); return { x: x, y: y }; } else { // Equation 21-27 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))); //console.log(A); // expecting 64501707.7 // Equations 21-24 / 25 / 26 var x=A * Math.cos(X) * Math.sin((la - la0) * degRad); var y=A * (Math.cos(X1) * Math.sin(X) - Math.sin(X1) * Math.cos(X) * Math.cos((la - la0) * degRad)); var k=A * Math.cos(X) / (a * m); //console.log('x', x); // expecting 971630.8 //console.log('y', y); // -1063049.3 //console.log('k', k); return { x: x, y: y }; } } modestmaps-js-3.3.6/examples/show-hide/000077500000000000000000000000001210227260500200135ustar00rootroot00000000000000modestmaps-js-3.3.6/examples/show-hide/index.html000066400000000000000000000026731210227260500220200ustar00rootroot00000000000000 display: none; example
toggle map visibility modestmaps-js-3.3.6/examples/simple/000077500000000000000000000000001210227260500174155ustar00rootroot00000000000000modestmaps-js-3.3.6/examples/simple/follower.js000066400000000000000000000040421210227260500216040ustar00rootroot00000000000000MM.Follower = function(map, location, content, dimensions) { this.coord = map.locationCoordinate(location); this.offset = new com.modestmaps.Point(0, 0); this.dimensions = dimensions || new com.modestmaps.Point(100, 50); this.padding = new com.modestmaps.Point(10, 10); this.offset = new com.modestmaps.Point(0, -this.dimensions.y); var follower = this; var callback = function(m, a) { return follower.draw(m); }; map.addCallback('drawn', callback); this.div = document.createElement('div'); this.div.style.position = 'absolute'; this.div.style.width = this.dimensions.x + 'px'; this.div.style.height = this.dimensions.y + 'px'; //this.div.style.backgroundColor = 'white'; //this.div.style.border = 'solid black 1px'; this.div.innerHTML = content; MM.addEvent(this.div, 'mousedown', function(e) { if(!e) e = window.event; return com.modestmaps.cancelEvent(e); }); map.parent.appendChild(this.div); this.draw(map); }; MM.Follower.prototype = { div: null, coord: null, offset: null, dimensions: null, padding: null, draw: function(map) { var point; try { point = map.coordinatePoint(this.coord); } catch(e) { // too soon? return; } if (point.x + this.dimensions.x + this.offset.x < 0) { // too far left this.div.style.display = 'none'; } else if(point.y + this.dimensions.y + this.offset.y < 0) { // too far up this.div.style.display = 'none'; } else if(point.x + this.offset.x > map.dimensions.x) { // too far right this.div.style.display = 'none'; } else if(point.y + this.offset.y > map.dimensions.y) { // too far down this.div.style.display = 'none'; } else { this.div.style.display = 'block'; this.div.style.left = point.x + this.offset.x + 'px'; this.div.style.top = point.y + this.offset.y + 'px'; } } }; modestmaps-js-3.3.6/examples/simple/index.html000066400000000000000000000025041210227260500214130ustar00rootroot00000000000000 Modest Maps JS

Modest Maps JS

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

The above div is a map initialized like so:

var layer = new MM.Layer(new MM.MapProvider(function(coord) {
    var img = parseInt(coord.zoom) +'-r'+ parseInt(coord.row) +'-c'+ parseInt(coord.column) + '.jpg';
    return 'http://osm-bayarea.s3.amazonaws.com/' + img;
}));

map = new MM.Map('map', layer, new MM.Point(900,900))

var f = new MM.Follower(map, new MM.Location(37.811530, -122.2666097), '° Broadway and Grand');

map.setCenterZoom(new MM.Location(37.811530, -122.2666097), 14);

Hands up who wants overlays?

modestmaps-js-3.3.6/examples/simple/minimal-maps.html000066400000000000000000000012451210227260500226710ustar00rootroot00000000000000 Modest Maps JS modestmaps-js-3.3.6/examples/spotlight/000077500000000000000000000000001210227260500201415ustar00rootroot00000000000000modestmaps-js-3.3.6/examples/spotlight/index.html000066400000000000000000000026571210227260500221500ustar00rootroot00000000000000 ModestMaps: Spotlight Effect with HTML Canvas

Spotlight effect

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

modestmaps-js-3.3.6/examples/spotlight/markers.html000066400000000000000000000154441210227260500225030ustar00rootroot00000000000000 ModestMaps JS: GeoJSON Markers + Spotlight Effect

ModestMaps JS: GeoJSON Markers + Spotlight Effect

This example mimics the behavior of Crimespotting (inspired by the Mac OS X feature), highlighting reports of the same type whenever you mouse over one.

This example requires a web browser with HTML canvas support.

modestmaps-js-3.3.6/examples/spotlight/modestmaps.markers.js000066400000000000000000000214231210227260500243200ustar00rootroot00000000000000// namespacing! if (!com) { var com = {}; } if (!com.modestmaps) { com.modestmaps = {}; } (function(MM) { /** * The MarkerLayer doesn't do any tile stuff, so it doesn't need to * inherit from MM.Layer. The constructor takes only an optional parent * element. * * Usage: * * // create the map with some constructor parameters * var map = new MM.Map(...); * // create a new MarkerLayer instance and add it to the map * var layer = new MM.MarkerLayer(); * map.addLayer(layer); * // create a marker element * var marker = document.createElement("a"); * marker.innerHTML = "Stamen"; * // add it to the layer at the specified geographic location * layer.addMarker(marker, new MM.Location(37.764, -122.419)); * // center the map on the marker's location * map.setCenterZoom(marker.location, 13); * */ MM.MarkerLayer = function(parent) { this.parent = parent || document.createElement('div'); this.parent.style.cssText = 'position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; margin: 0; padding: 0; z-index: 0'; this.markers = []; this.resetPosition(); }; MM.MarkerLayer.prototype = { // a list of our markers markers: null, // the absolute position of the parent element position: null, // the last coordinate we saw on the map lastCoord: null, draw: function() { // these are our previous and next map center coordinates var prev = this.lastCoord, next = this.map.pointCoordinate({x: 0, y: 0}); // if we've recorded the map's previous center... if (prev) { // if the zoom hasn't changed, find the delta in screen // coordinates and pan the parent element if (prev.zoom == next.zoom) { var p1 = this.map.coordinatePoint(prev), p2 = this.map.coordinatePoint(next), dx = p1.x - p2.x, dy = p1.y - p2.y; // console.log("panned:", [dx, dy]); this.onPanned(dx, dy); // otherwise, reposition all the markers } else { this.onZoomed(); } // otherwise, reposition all the markers } else { this.onZoomed(); } // remember the previous center this.lastCoord = next.copy(); }, // when zoomed, reset the position and reposition all markers onZoomed: function() { this.resetPosition(); this.repositionAllMarkers(); }, // when panned, offset the position by the provided screen coordinate x // and y values onPanned: function(dx, dy) { this.position.x += dx; this.position.y += dy; this.parent.style.left = ~~(this.position.x + .5) + "px"; this.parent.style.top = ~~(this.position.y + .5) + "px"; }, // remove all markers removeAllMarkers: function() { while (this.markers.length > 0) { this.removeMarker(this.markers[0]); } }, /** * Coerce the provided value into a Location instance. The following * values are supported: * * 1. MM.Location instances * 2. Object literals with numeric "lat" and "lon" properties * 3. A string in the form "lat,lon" * 4. GeoJSON objects with "Point" geometries * * This function throws an exception on error. */ coerceLocation: function(feature) { switch (typeof feature) { case "string": // "lat,lon" string return MM.Location.fromString(feature); case "object": // GeoJSON if (typeof feature.geometry === "object") { var geom = feature.geometry; switch (geom.type) { // Point geometries => MM.Location case "Point": // coerce the lat and lon values, just in case var lon = Number(geom.coordinates[0]), lat = Number(geom.coordinates[1]); return new MM.Location(lat, lon); } throw 'Unable to get the location of GeoJSON "' + geom.type + '" geometry!'; } else if (feature instanceof MM.Location || (typeof feature.lat !== "undefined" && typeof feature.lon !== "undefined")) { return feature; } else { throw 'Unknown location object; no "lat" and "lon" properties found!'; } break; case "undefined": throw 'Location "undefined"'; } }, /** * Add an HTML element as a marker, located at the position of the * provided GeoJSON feature, Location instance (or {lat,lon} object * literal), or "lat,lon" string. */ addMarker: function(marker, feature) { if (!marker || !feature) { return null; } // convert the feature to a Location instance marker.location = this.coerceLocation(feature); // remember the tile coordinate so we don't have to reproject every time marker.coord = this.map.locationCoordinate(marker.location); // position: absolute marker.style.position = "absolute"; // update the marker's position this.repositionMarker(marker); // append it to the DOM this.parent.appendChild(marker); // add it to the list this.markers.push(marker); return marker; }, /** * Remove the element marker from the layer and the DOM. */ removeMarker: function(marker) { var index = this.markers.indexOf(marker); if (index > -1) { this.markers.splice(index, 1); } if (marker.parentNode == this.parent) { this.parent.removeChild(marker); } return marker; }, // reset the absolute position of the layer's parent element resetPosition: function() { this.position = new MM.Point(0, 0); this.parent.style.left = this.parent.style.top = "0px"; }, // reposition a single marker element repositionMarker: function(marker) { if (marker.coord) { var pos = this.map.coordinatePoint(marker.coord); // offset by the layer parent position if x or y is non-zero if (this.position.x || this.position.y) { pos.x -= this.position.x; pos.y -= this.position.y; } marker.style.left = ~~(pos.x + .5) + "px"; marker.style.top = ~~(pos.y + .5) + "px"; } else { // TODO: throw an error? } }, // Reposition al markers repositionAllMarkers: function() { var len = this.markers.length; for (var i = 0; i < len; i++) { this.repositionMarker(this.markers[i]); } } }; // Array.indexOf polyfill courtesy of Mozilla MDN: // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf if (!Array.prototype.indexOf) { Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) { "use strict"; if (this === void 0 || this === null) { throw new TypeError(); } var t = Object(this); var len = t.length >>> 0; if (len === 0) { return -1; } var n = 0; if (arguments.length > 0) { n = Number(arguments[1]); if (n !== n) { // shortcut for verifying if it's NaN n = 0; } else if (n !== 0 && n !== Infinity && n !== -Infinity) { n = (n > 0 || -1) * Math.floor(Math.abs(n)); } } if (n >= len) { return -1; } var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0); for (; k < len; k++) { if (k in t && t[k] === searchElement) { return k; } } return -1; } } })(com.modestmaps); modestmaps-js-3.3.6/examples/spotlight/spotlight.js000066400000000000000000000104321210227260500225140ustar00rootroot00000000000000/** * A Spotlight object instance draws "punched out" circles on a canvas. The * first argument should be an HTML Canvas instance. The second is an optional * canvas fillStyle (presumably, any CSS color string, e.g. "rgba(0,0,0,.5)" * for 50% transparent black). */ var Spotlight = function(canvas, fillStyle) { this.canvas = canvas; this.ctx = canvas.getContext("2d"); this.clear(); if (fillStyle) { this.fillStyle = fillStyle; } }; Spotlight.prototype = { fillStyle: "rgba(0,0,0,.6)", radius: 40, // clearing resets the canvas and fills it with the fillStyle clear: function() { this.canvas.width = this.canvas.width; this.ctx.globalCompositeOperation = "source-over"; this.fill(); }, // fill the canvas with the color defined by fillStyle fill: function() { this.ctx.fillStyle = this.fillStyle; this.ctx.beginPath(); this.ctx.rect(0, 0, this.canvas.width, this.canvas.height); this.ctx.fill(); }, /** * Draw an array of points ({x, y}) as white circles. Each circle may * define its own radius, or we fall back on the value radius argument. */ drawPoints: function(points) { this.ctx.fillStyle = "white"; var TWO_PI = Math.PI * 2, radius = this.radius; /* * NOTE: we have to draw each circle as a distinct path because * otherwise their endpoints are connected as though each arc is a * subpath. */ for (var i = 0; i < points.length; i++) { var p = points[i], r = ("radius" in p) ? p.radius : radius; this.ctx.beginPath(); this.ctx.arc(p.x, p.y, r, 0, TWO_PI, true); this.ctx.closePath(); this.ctx.fill(); } }, /** * Draw an array of points ({x, y}) as circles "punched out" from the fill * color. This produces the "spotlight" effect. */ punchout: function(points) { var time = +new Date(); this.clear(); this.ctx.globalCompositeOperation = "destination-out"; this.drawPoints(points); console.log(points.length, "pts took", (new Date() - time), "ms"); } }; var SpotlightLayer = function(canvas, fillStyle) { this.parent = canvas || document.createElement("canvas"); this.parent.style.position = "absolute"; this.spotlight = new Spotlight(this.parent, fillStyle); this.locations = []; }; SpotlightLayer.prototype = { positioned: false, locations: null, addLocation: function(loc) { if (this.map) { loc.coord = this.map.locationCoordinate(loc); } this.locations.push(loc); this.draw(); }, removeLocation: function(loc) { var len = this.locations.length, removed = false; for (var i = 0; i < len; i++) { if (this.locations[i] === loc) { this.locations.splice(i, 1); removed = true; break; } } if (removed) { this.draw(); } }, addLocations: function(locs) { var len = locs.length; if (this.map) { for (var i = 0; i < len; i++) { locs[i].coord = this.map.locationCoordinate(locs[i]); } } for (var i = 0; i < len; i++) { this.locations.push(locs[i]); } this.draw(); }, removeAllLocations: function() { this.locations = []; this.draw(); }, draw: function() { var map = this.map, canvas = this.parent; if (canvas.parentNode != map.parent) { map.parent.appendChild(canvas); } canvas.width = map.dimensions.x; canvas.height = map.dimensions.y; if (this.locations && this.locations.length) { var points = this.locations.map(function(loc) { var coord = loc.coord || (loc.coord = map.locationCoordinate(loc)), point = map.coordinatePoint(coord); if ("radius" in loc) point.radius = loc.radius; return point; }); this.spotlight.punchout(points); } else { this.spotlight.clear(); } } }; modestmaps-js-3.3.6/examples/sprite-tiles/000077500000000000000000000000001210227260500205505ustar00rootroot00000000000000modestmaps-js-3.3.6/examples/sprite-tiles/index.html000066400000000000000000000045121210227260500225470ustar00rootroot00000000000000 ModestMaps JS: Sprite Tiles

ModestMaps JS: Sprite Tiles

+1ft sea level rise & storm surge

modestmaps-js-3.3.6/examples/sprite-tiles/modestmaps.sprite-tiles.js000066400000000000000000000066471210227260500257220ustar00rootroot00000000000000(function(MM) { /** * The SpriteMapProvider creates
tiles with a background-image CSS * property of the tile URL, and scrolls the background image using a * provided offset, via setOffset(). The offsets are multiples of the * tile size, so use 1 to get a 256px offset instead of 256. Usage: * * var provider = new SpriteMapProvider(new mm.TemplatedMapProvider(...)); * provider.setOffset(1); // offsets vertically by 256px * provider.setOffset(2); // by 512px */ MM.SpriteMapProvider = function(template_provider) { this.template_provider = template_provider; this.offset = 0; this.tiles = {}; // FIXME: this is hard-coded now, but it should come from the map or the provider. // Currently, though, there's no way for the provider to know about the map. this.tileSize = new MM.Point(256, 256); }; MM.SpriteMapProvider.prototype = { tiles: null, tileSize: null, offset: 0, getOffset: function() { return this.offset; }, setOffset: function(offset) { if (this.offset != offset) { this.offset = offset; this.applyOffsets(); } }, applyOffset: function(tile) { var left = 0, top = -this.offset * this.tileSize.y; tile.style.backgroundPosition = left + "px " + top + "px"; }, applyOffsets: function() { for (var key in this.tiles) { this.applyOffset(this.tiles[key]); } }, getTile: function(coord) { var key = coord.toKey(); if (this.tiles.hasOwnProperty(key)) { return this.tiles[key]; } else { var url = this.template_provider.getTileUrl(coord); if (url) { var tile = document.createElement("div"); tile.style.backgroundRepeat = "no-repeat"; tile.backgroundTimeout = setTimeout(function() { tile.style.backgroundImage = "url(" + url + ")"; }, 100); /* * OTE: because using matrix transforms invalidates * explicit width and height values, we need to put a * "strut" inside each tile div that provides its intrinsic * size. This has the awesome side benefit of scaling * automatically. */ var strut = tile.appendChild(document.createElement("span")); strut.style.display = "block"; strut.style.width = this.tileSize.x + "px"; strut.style.height = this.tileSize.y + "px"; this.tiles[key] = tile; this.applyOffset(tile); return tile; } else { return null; } } }, releaseTile: function(coord) { var key = coord.toKey(), tile = this.tiles[key]; // clearTimeout(tile.backgroundTimeout); delete this.tiles[key]; }, reAddTile: function(key, coord, tile) { // console.log("re-add:", key, tile); this.tiles[key] = tile; this.applyOffset(tile); } }; })(com.modestmaps); modestmaps-js-3.3.6/examples/static/000077500000000000000000000000001210227260500174135ustar00rootroot00000000000000modestmaps-js-3.3.6/examples/static/index.html000066400000000000000000000022251210227260500214110ustar00rootroot00000000000000 Modest Maps JS

Modest Maps JS

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

The above div is a map initialized like so:

// "import" the namespace
var provider = new MM.MapProvider(function(coord) {
    var img = parseInt(coord.zoom) +'-r'+ parseInt(coord.row) +'-c'+ parseInt(coord.column) + '.jpg';
    return 'http://osm-bayarea.s3.amazonaws.com/' + img;
});

map = new MM.Map('map', provider, new MM.Point(900,900), [])

map.setCenterZoom(new MM.Location(37.811530, -122.2666097), 14);
modestmaps-js-3.3.6/examples/templated/000077500000000000000000000000001210227260500201035ustar00rootroot00000000000000modestmaps-js-3.3.6/examples/templated/follower.js000066400000000000000000000046441210227260500223020ustar00rootroot00000000000000// namespacing! if (!com) { var com = { }; if (!com.modestmaps) { com.modestmaps = { }; } } com.modestmaps.Follower = function(map, location, content, dimensions) { this.coord = map.locationCoordinate(location); this.offset = new com.modestmaps.Point(0, 0); this.dimensions = dimensions || new com.modestmaps.Point(100, 50); this.padding = new com.modestmaps.Point(10, 10); this.offset = new com.modestmaps.Point(0, -this.dimensions.y); var follower = this; var callback = function(m, a) { return follower.draw(m); }; map.addCallback('panned', callback); map.addCallback('zoomed', callback); map.addCallback('centered', callback); map.addCallback('extentset', callback); map.addCallback('resized', callback); this.div = document.createElement('div'); this.div.style.position = 'absolute'; this.div.style.width = this.dimensions.x + 'px'; this.div.style.height = this.dimensions.y + 'px'; //this.div.style.backgroundColor = 'white'; //this.div.style.border = 'solid black 1px'; this.div.innerHTML = content; com.modestmaps.addEvent(this.div, 'mousedown', function(e) { if(!e) e = window.event; return com.modestmaps.cancelEvent(e); }); map.parent.appendChild(this.div); this.draw(map); } com.modestmaps.Follower.prototype = { div: null, coord: null, offset: null, dimensions: null, padding: null, draw: function(map) { try { var point = map.coordinatePoint(this.coord); } catch(e) { // too soon? return; } if(point.x + this.dimensions.x + this.offset.x < 0) { // too far left this.div.style.display = 'none'; } else if(point.y + this.dimensions.y + this.offset.y < 0) { // too far up this.div.style.display = 'none'; } else if(point.x + this.offset.x > map.dimensions.x) { // too far right this.div.style.display = 'none'; } else if(point.y + this.offset.y > map.dimensions.y) { // too far down this.div.style.display = 'none'; } else { this.div.style.display = 'block'; this.div.style.left = point.x + this.offset.x + 'px'; this.div.style.top = point.y + this.offset.y + 'px'; } } }; modestmaps-js-3.3.6/examples/templated/index.html000066400000000000000000000025741210227260500221100ustar00rootroot00000000000000 Modest Maps JS

Modest Maps JS

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

modestmaps-js-3.3.6/examples/tilecache/000077500000000000000000000000001210227260500200455ustar00rootroot00000000000000modestmaps-js-3.3.6/examples/tilecache/index.html000066400000000000000000000016741210227260500220520ustar00rootroot00000000000000 Modest Maps JS

Modest Maps JS

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

modestmaps-js-3.3.6/examples/tilecache/tilecache.js000066400000000000000000000030121210227260500223200ustar00rootroot00000000000000MM.TileCacheMapProvider = function(template, subdomains) { // utility functions... function addZeros(i, zeros) { if (zeros === undefined) { zeros = 3; } var s = i.toString(); while (s.length < zeros) { s = '0' + s; } return s; } function tilePad(i) { return addZeros(parseInt(i / 1000000, 10)) + '/' + addZeros(parseInt(i / 1000, 10)) + '/' + addZeros(i % 1000); } // this is like a call to 'super', kinda... // we end up initializing a basic modestmaps.js MapProvider with // a getTileURL function that knows about the above utility functions MM.MapProvider.call(this, function(coord) { coord = this.sourceCoordinate(coord); if (!coord) { return null; } var mod = coord.copy(); // fill in coordinates into URL var url = template.replace('{Z}', mod.zoom) .replace('{X}', tilePad(mod.column)) .replace('{Y}', tilePad(Math.pow(2, mod.zoom) - 1 - mod.row)); // replace the {S} portion of the url with the appropriate subdomain if (url.indexOf('{S}') > -1) { var subdomain = (subdomains && subdomains.length > 0) ? subdomains[parseInt(mod.row + mod.column, 10) % subdomains.length] : ''; url = url.replace('{S}', subdomain ? subdomain + '.' : ''); } return url; }); }; MM.extend(MM.TileCacheMapProvider, MM.MapProvider); modestmaps-js-3.3.6/examples/touch/000077500000000000000000000000001210227260500172465ustar00rootroot00000000000000modestmaps-js-3.3.6/examples/touch/index.html000066400000000000000000000035331210227260500212470ustar00rootroot00000000000000 Modest Maps JS - Touch Tester
modestmaps-js-3.3.6/examples/twomaps/000077500000000000000000000000001210227260500176165ustar00rootroot00000000000000modestmaps-js-3.3.6/examples/twomaps/index.html000066400000000000000000000054451210227260500216230ustar00rootroot00000000000000 Modest Maps JS

Modest Maps JS

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

modestmaps-js-3.3.6/examples/zoombox/000077500000000000000000000000001210227260500176215ustar00rootroot00000000000000modestmaps-js-3.3.6/examples/zoombox/index.html000066400000000000000000000015041210227260500216160ustar00rootroot00000000000000 Modest Maps JS - Zoom Box
modestmaps-js-3.3.6/examples/zoombox/zoombox.js000066400000000000000000000062101210227260500216530ustar00rootroot00000000000000com.modestmaps.ZoomBox = function(map) { this.map = map; var theBox = this; this.getMousePoint = function(e) { // start with just the mouse (x, y) var point = new com.modestmaps.Point(e.clientX, e.clientY); // correct for scrolled document point.x += document.body.scrollLeft + document.documentElement.scrollLeft; point.y += document.body.scrollTop + document.documentElement.scrollTop; // correct for nested offsets in DOM for(var node = this.map.parent; node; node = node.offsetParent) { point.x -= node.offsetLeft; point.y -= node.offsetTop; } return point; }; var boxDiv = document.createElement('div'); boxDiv.id = map.parent.id+'-zoombox'; boxDiv.style.cssText = 'margin:0; padding:0; position:absolute; top:0; left:0;' boxDiv.style.width = map.dimensions.x+'px'; boxDiv.style.height = map.dimensions.y+'px'; map.parent.appendChild(boxDiv); var box = document.createElement('div'); box.id = map.parent.id+'-zoombox-box'; 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;'; boxDiv.appendChild(box); // TODO: respond to resize var mouseDownPoint = null; this.mouseDown = function(e) { if (e.shiftKey) { mouseDownPoint = theBox.getMousePoint(e); box.style.left = mouseDownPoint.x + 'px'; box.style.top = mouseDownPoint.y + 'px'; com.modestmaps.addEvent(map.parent, 'mousemove', theBox.mouseMove); com.modestmaps.addEvent(map.parent, 'mouseup', theBox.mouseUp); map.parent.style.cursor = 'crosshair'; return com.modestmaps.cancelEvent(e); } }; this.mouseMove = function(e) { var point = theBox.getMousePoint(e); box.style.display = 'block'; if (point.x < mouseDownPoint.x) { box.style.left = point.x + 'px'; } else { box.style.left = mouseDownPoint.x + 'px'; } box.style.width = Math.abs(point.x - mouseDownPoint.x) + 'px'; if (point.y < mouseDownPoint.y) { box.style.top = point.y + 'px'; } else { box.style.top = mouseDownPoint.y + 'px'; } box.style.height = Math.abs(point.y - mouseDownPoint.y) + 'px'; return com.modestmaps.cancelEvent(e); }; this.mouseUp = function(e) { var point = theBox.getMousePoint(e); var l1 = map.pointLocation(point); var l2 = map.pointLocation(mouseDownPoint); map.setExtent([l1,l2]); box.style.display = 'none'; com.modestmaps.removeEvent(map.parent, 'mousemove', theBox.mouseMove); com.modestmaps.removeEvent(map.parent, 'mouseup', theBox.mouseUp); map.parent.style.cursor = 'auto'; return com.modestmaps.cancelEvent(e); }; com.modestmaps.addEvent(boxDiv, 'mousedown', this.mouseDown); } modestmaps-js-3.3.6/examples/zoompan/000077500000000000000000000000001210227260500176075ustar00rootroot00000000000000modestmaps-js-3.3.6/examples/zoompan/index.html000066400000000000000000000156561210227260500216210ustar00rootroot00000000000000 Modest Maps JS - Smooth Efficient Zooming and Panning
modestmaps-js-3.3.6/modestmaps.js000066400000000000000000003062451210227260500170320ustar00rootroot00000000000000/*! * Modest Maps JS v3.3.6 * http://modestmaps.com/ * * Copyright (c) 2011 Stamen Design, All Rights Reserved. * * Open source under the BSD License. * http://creativecommons.org/licenses/BSD/ * * Versioned using Semantic Versioning (v.major.minor.patch) * See CHANGELOG and http://semver.org/ for more details. * */ var previousMM = MM; // namespacing for backwards-compatibility if (!com) { var com = {}; if (!com.modestmaps) com.modestmaps = {}; } var MM = com.modestmaps = { noConflict: function() { MM = previousMM; return this; } }; (function(MM) { // Make inheritance bearable: clone one level of properties MM.extend = function(child, parent) { for (var property in parent.prototype) { if (typeof child.prototype[property] == "undefined") { child.prototype[property] = parent.prototype[property]; } } return child; }; MM.getFrame = function () { // native animation frames // http://webstuff.nfshost.com/anim-timing/Overview.html // http://dev.chromium.org/developers/design-documents/requestanimationframe-implementation // http://paulirish.com/2011/requestanimationframe-for-smart-animating/ // can't apply these directly to MM because Chrome needs window // to own webkitRequestAnimationFrame (for example) // perhaps we should namespace an alias onto window instead? // e.g. window.mmRequestAnimationFrame? return function(callback) { (window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) { window.setTimeout(function () { callback(+new Date()); }, 10); })(callback); }; }(); // Inspired by LeafletJS MM.transformProperty = (function(props) { if (!this.document) return; // node.js safety var style = document.documentElement.style; for (var i = 0; i < props.length; i++) { if (props[i] in style) { return props[i]; } } return false; })(['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']); MM.matrixString = function(point) { // Make the result of point.scale * point.width a whole number. if (point.scale * point.width % 1) { point.scale += (1 - point.scale * point.width % 1) / point.width; } var scale = point.scale || 1; if (MM._browser.webkit3d) { return 'translate3d(' + point.x.toFixed(0) + 'px,' + point.y.toFixed(0) + 'px, 0px)' + 'scale3d(' + scale + ',' + scale + ', 1)'; } else { return 'translate(' + point.x.toFixed(6) + 'px,' + point.y.toFixed(6) + 'px)' + 'scale(' + scale + ',' + scale + ')'; } }; MM._browser = (function(window) { return { webkit: ('WebKitCSSMatrix' in window), webkit3d: ('WebKitCSSMatrix' in window) && ('m11' in new WebKitCSSMatrix()) }; })(this); // use this for node.js global MM.moveElement = function(el, point) { if (MM.transformProperty) { // Optimize for identity transforms, where you don't actually // need to change this element's string. Browsers can optimize for // the .style.left case but not for this CSS case. if (!point.scale) point.scale = 1; if (!point.width) point.width = 0; if (!point.height) point.height = 0; var ms = MM.matrixString(point); if (el[MM.transformProperty] !== ms) { el.style[MM.transformProperty] = el[MM.transformProperty] = ms; } } else { el.style.left = point.x + 'px'; el.style.top = point.y + 'px'; // Don't set width unless asked to: this is performance-intensive // and not always necessary if (point.width && point.height && point.scale) { el.style.width = Math.ceil(point.width * point.scale) + 'px'; el.style.height = Math.ceil(point.height * point.scale) + 'px'; } } }; // Events // Cancel an event: prevent it from bubbling MM.cancelEvent = function(e) { // there's more than one way to skin this cat e.cancelBubble = true; e.cancel = true; e.returnValue = false; if (e.stopPropagation) { e.stopPropagation(); } if (e.preventDefault) { e.preventDefault(); } return false; }; MM.coerceLayer = function(layerish) { if (typeof layerish == 'string') { // Probably a template string return new MM.Layer(new MM.TemplatedLayer(layerish)); } else if ('draw' in layerish && typeof layerish.draw == 'function') { // good enough, though we should probably enforce .parent and .destroy() too return layerish; } else { // probably a MapProvider return new MM.Layer(layerish); } }; // see http://ejohn.org/apps/jselect/event.html for the originals MM.addEvent = function(obj, type, fn) { if (obj.addEventListener) { obj.addEventListener(type, fn, false); if (type == 'mousewheel') { obj.addEventListener('DOMMouseScroll', fn, false); } } else if (obj.attachEvent) { obj['e'+type+fn] = fn; obj[type+fn] = function(){ obj['e'+type+fn](window.event); }; obj.attachEvent('on'+type, obj[type+fn]); } }; MM.removeEvent = function( obj, type, fn ) { if (obj.removeEventListener) { obj.removeEventListener(type, fn, false); if (type == 'mousewheel') { obj.removeEventListener('DOMMouseScroll', fn, false); } } else if (obj.detachEvent) { obj.detachEvent('on'+type, obj[type+fn]); obj[type+fn] = null; } }; // Cross-browser function to get current element style property MM.getStyle = function(el,styleProp) { if (el.currentStyle) return el.currentStyle[styleProp]; else if (window.getComputedStyle) return document.defaultView.getComputedStyle(el,null).getPropertyValue(styleProp); }; // Point MM.Point = function(x, y) { this.x = parseFloat(x); this.y = parseFloat(y); }; MM.Point.prototype = { x: 0, y: 0, toString: function() { return "(" + this.x.toFixed(3) + ", " + this.y.toFixed(3) + ")"; }, copy: function() { return new MM.Point(this.x, this.y); } }; // Get the euclidean distance between two points MM.Point.distance = function(p1, p2) { return Math.sqrt( Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2)); }; // Get a point between two other points, biased by `t`. MM.Point.interpolate = function(p1, p2, t) { return new MM.Point( p1.x + (p2.x - p1.x) * t, p1.y + (p2.y - p1.y) * t); }; // Coordinate // ---------- // An object representing a tile position, at as specified zoom level. // This is not necessarily a precise tile - `row`, `column`, and // `zoom` can be floating-point numbers, and the `container()` function // can be used to find the actual tile that contains the point. MM.Coordinate = function(row, column, zoom) { this.row = row; this.column = column; this.zoom = zoom; }; MM.Coordinate.prototype = { row: 0, column: 0, zoom: 0, toString: function() { return "(" + this.row.toFixed(3) + ", " + this.column.toFixed(3) + " @" + this.zoom.toFixed(3) + ")"; }, // Quickly generate a string representation of this coordinate to // index it in hashes. toKey: function() { // We've tried to use efficient hash functions here before but we took // them out. Contributions welcome but watch out for collisions when the // row or column are negative and check thoroughly (exhaustively) before // committing. return this.zoom + ',' + this.row + ',' + this.column; }, // Clone this object. copy: function() { return new MM.Coordinate(this.row, this.column, this.zoom); }, // Get the actual, rounded-number tile that contains this point. container: function() { // using floor here (not parseInt, ~~) because we want -0.56 --> -1 return new MM.Coordinate(Math.floor(this.row), Math.floor(this.column), Math.floor(this.zoom)); }, // Recalculate this Coordinate at a different zoom level and return the // new object. zoomTo: function(destination) { var power = Math.pow(2, destination - this.zoom); return new MM.Coordinate(this.row * power, this.column * power, destination); }, // Recalculate this Coordinate at a different relative zoom level and return the // new object. zoomBy: function(distance) { var power = Math.pow(2, distance); return new MM.Coordinate(this.row * power, this.column * power, this.zoom + distance); }, // Move this coordinate up by `dist` coordinates up: function(dist) { if (dist === undefined) dist = 1; return new MM.Coordinate(this.row - dist, this.column, this.zoom); }, // Move this coordinate right by `dist` coordinates right: function(dist) { if (dist === undefined) dist = 1; return new MM.Coordinate(this.row, this.column + dist, this.zoom); }, // Move this coordinate down by `dist` coordinates down: function(dist) { if (dist === undefined) dist = 1; return new MM.Coordinate(this.row + dist, this.column, this.zoom); }, // Move this coordinate left by `dist` coordinates left: function(dist) { if (dist === undefined) dist = 1; return new MM.Coordinate(this.row, this.column - dist, this.zoom); } }; // Location // -------- MM.Location = function(lat, lon) { this.lat = parseFloat(lat); this.lon = parseFloat(lon); }; MM.Location.prototype = { lat: 0, lon: 0, toString: function() { return "(" + this.lat.toFixed(3) + ", " + this.lon.toFixed(3) + ")"; }, copy: function() { return new MM.Location(this.lat, this.lon); } }; // returns approximate distance between start and end locations // // default unit is meters // // you can specify different units by optionally providing the // earth's radius in the units you desire // // Default is 6,378,000 metres, suggested values are: // // * 3963.1 statute miles // * 3443.9 nautical miles // * 6378 km // // see [Formula and code for calculating distance based on two lat/lon locations](http://jan.ucc.nau.edu/~cvm/latlon_formula.html) MM.Location.distance = function(l1, l2, r) { if (!r) { // default to meters r = 6378000; } var deg2rad = Math.PI / 180.0, a1 = l1.lat * deg2rad, b1 = l1.lon * deg2rad, a2 = l2.lat * deg2rad, b2 = l2.lon * deg2rad, c = Math.cos(a1) * Math.cos(b1) * Math.cos(a2) * Math.cos(b2), d = Math.cos(a1) * Math.sin(b1) * Math.cos(a2) * Math.sin(b2), e = Math.sin(a1) * Math.sin(a2); return Math.acos(c + d + e) * r; }; // Interpolates along a great circle, f between 0 and 1 // // * FIXME: could be heavily optimized (lots of trig calls to cache) // * FIXME: could be inmproved for calculating a full path MM.Location.interpolate = function(l1, l2, f) { if (l1.lat === l2.lat && l1.lon === l2.lon) { return new MM.Location(l1.lat, l1.lon); } var deg2rad = Math.PI / 180.0, lat1 = l1.lat * deg2rad, lon1 = l1.lon * deg2rad, lat2 = l2.lat * deg2rad, lon2 = l2.lon * deg2rad; var d = 2 * Math.asin( Math.sqrt( Math.pow(Math.sin((lat1 - lat2) / 2), 2) + Math.cos(lat1) * Math.cos(lat2) * Math.pow(Math.sin((lon1 - lon2) / 2), 2))); var A = Math.sin((1-f)*d)/Math.sin(d); var B = Math.sin(f*d)/Math.sin(d); var x = A * Math.cos(lat1) * Math.cos(lon1) + B * Math.cos(lat2) * Math.cos(lon2); var y = A * Math.cos(lat1) * Math.sin(lon1) + B * Math.cos(lat2) * Math.sin(lon2); var z = A * Math.sin(lat1) + B * Math.sin(lat2); var latN = Math.atan2(z, Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2))); var lonN = Math.atan2(y,x); return new MM.Location(latN / deg2rad, lonN / deg2rad); }; // Returns bearing from one point to another // // * FIXME: bearing is not constant along significant great circle arcs. MM.Location.bearing = function(l1, l2) { var deg2rad = Math.PI / 180.0, lat1 = l1.lat * deg2rad, lon1 = l1.lon * deg2rad, lat2 = l2.lat * deg2rad, lon2 = l2.lon * deg2rad; var result = Math.atan2( Math.sin(lon1 - lon2) * Math.cos(lat2), Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(lon1 - lon2) ) / -(Math.PI / 180); // map it into 0-360 range return (result < 0) ? result + 360 : result; }; // Extent // ---------- // An object representing a map's rectangular extent, defined by its north, // south, east and west bounds. MM.Extent = function(north, west, south, east) { if (north instanceof MM.Location && west instanceof MM.Location) { var northwest = north, southeast = west; north = northwest.lat; west = northwest.lon; south = southeast.lat; east = southeast.lon; } if (isNaN(south)) south = north; if (isNaN(east)) east = west; this.north = Math.max(north, south); this.south = Math.min(north, south); this.east = Math.max(east, west); this.west = Math.min(east, west); }; MM.Extent.prototype = { // boundary attributes north: 0, south: 0, east: 0, west: 0, copy: function() { return new MM.Extent(this.north, this.west, this.south, this.east); }, toString: function(precision) { if (isNaN(precision)) precision = 3; return [ this.north.toFixed(precision), this.west.toFixed(precision), this.south.toFixed(precision), this.east.toFixed(precision) ].join(", "); }, // getters for the corner locations northWest: function() { return new MM.Location(this.north, this.west); }, southEast: function() { return new MM.Location(this.south, this.east); }, northEast: function() { return new MM.Location(this.north, this.east); }, southWest: function() { return new MM.Location(this.south, this.west); }, // getter for the center location center: function() { return new MM.Location( this.south + (this.north - this.south) / 2, this.east + (this.west - this.east) / 2 ); }, // extend the bounds to include a location's latitude and longitude encloseLocation: function(loc) { if (loc.lat > this.north) this.north = loc.lat; if (loc.lat < this.south) this.south = loc.lat; if (loc.lon > this.east) this.east = loc.lon; if (loc.lon < this.west) this.west = loc.lon; }, // extend the bounds to include multiple locations encloseLocations: function(locations) { var len = locations.length; for (var i = 0; i < len; i++) { this.encloseLocation(locations[i]); } }, // reset bounds from a list of locations setFromLocations: function(locations) { var len = locations.length, first = locations[0]; this.north = this.south = first.lat; this.east = this.west = first.lon; for (var i = 1; i < len; i++) { this.encloseLocation(locations[i]); } }, // extend the bounds to include another extent encloseExtent: function(extent) { if (extent.north > this.north) this.north = extent.north; if (extent.south < this.south) this.south = extent.south; if (extent.east > this.east) this.east = extent.east; if (extent.west < this.west) this.west = extent.west; }, // determine if a location is within this extent containsLocation: function(loc) { return loc.lat >= this.south && loc.lat <= this.north && loc.lon >= this.west && loc.lon <= this.east; }, // turn an extent into an array of locations containing its northwest // and southeast corners (used in MM.Map.setExtent()) toArray: function() { return [this.northWest(), this.southEast()]; } }; MM.Extent.fromString = function(str) { var parts = str.split(/\s*,\s*/); if (parts.length != 4) { throw "Invalid extent string (expecting 4 comma-separated numbers)"; } return new MM.Extent( parseFloat(parts[0]), parseFloat(parts[1]), parseFloat(parts[2]), parseFloat(parts[3]) ); }; MM.Extent.fromArray = function(locations) { var extent = new MM.Extent(); extent.setFromLocations(locations); return extent; }; // Transformation // -------------- MM.Transformation = function(ax, bx, cx, ay, by, cy) { this.ax = ax; this.bx = bx; this.cx = cx; this.ay = ay; this.by = by; this.cy = cy; }; MM.Transformation.prototype = { ax: 0, bx: 0, cx: 0, ay: 0, by: 0, cy: 0, transform: function(point) { return new MM.Point(this.ax * point.x + this.bx * point.y + this.cx, this.ay * point.x + this.by * point.y + this.cy); }, untransform: function(point) { return new MM.Point((point.x * this.by - point.y * this.bx - this.cx * this.by + this.cy * this.bx) / (this.ax * this.by - this.ay * this.bx), (point.x * this.ay - point.y * this.ax - this.cx * this.ay + this.cy * this.ax) / (this.bx * this.ay - this.by * this.ax)); } }; // Generates a transform based on three pairs of points, // a1 -> a2, b1 -> b2, c1 -> c2. MM.deriveTransformation = function(a1x, a1y, a2x, a2y, b1x, b1y, b2x, b2y, c1x, c1y, c2x, c2y) { var x = MM.linearSolution(a1x, a1y, a2x, b1x, b1y, b2x, c1x, c1y, c2x); var y = MM.linearSolution(a1x, a1y, a2y, b1x, b1y, b2y, c1x, c1y, c2y); return new MM.Transformation(x[0], x[1], x[2], y[0], y[1], y[2]); }; // Solves a system of linear equations. // // t1 = (a * r1) + (b + s1) + c // t2 = (a * r2) + (b + s2) + c // t3 = (a * r3) + (b + s3) + c // // r1 - t3 are the known values. // a, b, c are the unknowns to be solved. // returns the a, b, c coefficients. MM.linearSolution = function(r1, s1, t1, r2, s2, t2, r3, s3, t3) { // make them all floats r1 = parseFloat(r1); s1 = parseFloat(s1); t1 = parseFloat(t1); r2 = parseFloat(r2); s2 = parseFloat(s2); t2 = parseFloat(t2); r3 = parseFloat(r3); s3 = parseFloat(s3); t3 = parseFloat(t3); var a = (((t2 - t3) * (s1 - s2)) - ((t1 - t2) * (s2 - s3))) / (((r2 - r3) * (s1 - s2)) - ((r1 - r2) * (s2 - s3))); var b = (((t2 - t3) * (r1 - r2)) - ((t1 - t2) * (r2 - r3))) / (((s2 - s3) * (r1 - r2)) - ((s1 - s2) * (r2 - r3))); var c = t1 - (r1 * a) - (s1 * b); return [ a, b, c ]; }; // Projection // ---------- // An abstract class / interface for projections MM.Projection = function(zoom, transformation) { if (!transformation) { transformation = new MM.Transformation(1, 0, 0, 0, 1, 0); } this.zoom = zoom; this.transformation = transformation; }; MM.Projection.prototype = { zoom: 0, transformation: null, rawProject: function(point) { throw "Abstract method not implemented by subclass."; }, rawUnproject: function(point) { throw "Abstract method not implemented by subclass."; }, project: function(point) { point = this.rawProject(point); if(this.transformation) { point = this.transformation.transform(point); } return point; }, unproject: function(point) { if(this.transformation) { point = this.transformation.untransform(point); } point = this.rawUnproject(point); return point; }, locationCoordinate: function(location) { var point = new MM.Point(Math.PI * location.lon / 180.0, Math.PI * location.lat / 180.0); point = this.project(point); return new MM.Coordinate(point.y, point.x, this.zoom); }, coordinateLocation: function(coordinate) { coordinate = coordinate.zoomTo(this.zoom); var point = new MM.Point(coordinate.column, coordinate.row); point = this.unproject(point); return new MM.Location(180.0 * point.y / Math.PI, 180.0 * point.x / Math.PI); } }; // A projection for equilateral maps, based on longitude and latitude MM.LinearProjection = function(zoom, transformation) { MM.Projection.call(this, zoom, transformation); }; // The Linear projection doesn't reproject points MM.LinearProjection.prototype = { rawProject: function(point) { return new MM.Point(point.x, point.y); }, rawUnproject: function(point) { return new MM.Point(point.x, point.y); } }; MM.extend(MM.LinearProjection, MM.Projection); MM.MercatorProjection = function(zoom, transformation) { // super! MM.Projection.call(this, zoom, transformation); }; // Project lon/lat points into meters required for Mercator MM.MercatorProjection.prototype = { rawProject: function(point) { return new MM.Point(point.x, Math.log(Math.tan(0.25 * Math.PI + 0.5 * point.y))); }, rawUnproject: function(point) { return new MM.Point(point.x, 2 * Math.atan(Math.pow(Math.E, point.y)) - 0.5 * Math.PI); } }; MM.extend(MM.MercatorProjection, MM.Projection); // Providers // --------- // Providers provide tile URLs and possibly elements for layers. // // MapProvider -> // Template // MM.MapProvider = function(getTile) { if (getTile) { this.getTile = getTile; } }; MM.MapProvider.prototype = { // these are limits for available *tiles* // panning limits will be different (since you can wrap around columns) // but if you put Infinity in here it will screw up sourceCoordinate tileLimits: [ new MM.Coordinate(0,0,0), // top left outer new MM.Coordinate(1,1,0).zoomTo(18) // bottom right inner ], getTileUrl: function(coordinate) { throw "Abstract method not implemented by subclass."; }, getTile: function(coordinate) { throw "Abstract method not implemented by subclass."; }, // releaseTile is not required releaseTile: function(element) { }, // use this to tell MapProvider that tiles only exist between certain zoom levels. // should be set separately on Map to restrict interactive zoom/pan ranges setZoomRange: function(minZoom, maxZoom) { this.tileLimits[0] = this.tileLimits[0].zoomTo(minZoom); this.tileLimits[1] = this.tileLimits[1].zoomTo(maxZoom); }, // wrap column around the world if necessary // return null if wrapped coordinate is outside of the tile limits sourceCoordinate: function(coord) { var TL = this.tileLimits[0].zoomTo(coord.zoom).container(), BR = this.tileLimits[1].zoomTo(coord.zoom), columnSize = Math.pow(2, coord.zoom), wrappedColumn; BR = new MM.Coordinate(Math.ceil(BR.row), Math.ceil(BR.column), Math.floor(BR.zoom)); if (coord.column < 0) { wrappedColumn = ((coord.column % columnSize) + columnSize) % columnSize; } else { wrappedColumn = coord.column % columnSize; } if (coord.row < TL.row || coord.row >= BR.row) { return null; } else if (wrappedColumn < TL.column || wrappedColumn >= BR.column) { return null; } else { return new MM.Coordinate(coord.row, wrappedColumn, coord.zoom); } } }; /** * FIXME: need a better explanation here! This is a pretty crucial part of * understanding how to use ModestMaps. * * TemplatedMapProvider is a tile provider that generates tile URLs from a * template string by replacing the following bits for each tile * coordinate: * * {Z}: the tile's zoom level (from 1 to ~20) * {X}: the tile's X, or column (from 0 to a very large number at higher * zooms) * {Y}: the tile's Y, or row (from 0 to a very large number at higher * zooms) * * E.g.: * * var osm = new MM.TemplatedMapProvider("http://tile.openstreetmap.org/{Z}/{X}/{Y}.png"); * * Or: * * var placeholder = new MM.TemplatedMapProvider("http://placehold.it/256/f0f/fff.png&text={Z}/{X}/{Y}"); * */ MM.Template = function(template, subdomains) { var isQuadKey = template.match(/{(Q|quadkey)}/); // replace Microsoft style substitution strings if (isQuadKey) template = template .replace('{subdomains}', '{S}') .replace('{zoom}', '{Z}') .replace('{quadkey}', '{Q}'); var hasSubdomains = (subdomains && subdomains.length && template.indexOf("{S}") >= 0); function quadKey (row, column, zoom) { var key = ''; for (var i = 1; i <= zoom; i++) { key += (((row >> zoom - i) & 1) << 1) | ((column >> zoom - i) & 1); } return key || '0'; } var getTileUrl = function(coordinate) { var coord = this.sourceCoordinate(coordinate); if (!coord) { return null; } var base = template; if (hasSubdomains) { var index = parseInt(coord.zoom + coord.row + coord.column, 10) % subdomains.length; base = base.replace('{S}', subdomains[index]); } if (isQuadKey) { return base .replace('{Z}', coord.zoom.toFixed(0)) .replace('{Q}', quadKey(coord.row, coord.column, coord.zoom)); } else { return base .replace('{Z}', coord.zoom.toFixed(0)) .replace('{X}', coord.column.toFixed(0)) .replace('{Y}', coord.row.toFixed(0)); } }; MM.MapProvider.call(this, getTileUrl); }; MM.Template.prototype = { // quadKey generator getTile: function(coord) { return this.getTileUrl(coord); } }; MM.extend(MM.Template, MM.MapProvider); MM.TemplatedLayer = function(template, subdomains, name) { return new MM.Layer(new MM.Template(template, subdomains), null, name); }; // Event Handlers // -------------- // A utility function for finding the offset of the // mouse from the top-left of the page MM.getMousePoint = function(e, map) { // start with just the mouse (x, y) var point = new MM.Point(e.clientX, e.clientY); // correct for scrolled document point.x += document.body.scrollLeft + document.documentElement.scrollLeft; point.y += document.body.scrollTop + document.documentElement.scrollTop; // correct for nested offsets in DOM for (var node = map.parent; node; node = node.offsetParent) { point.x -= node.offsetLeft; point.y -= node.offsetTop; } return point; }; MM.MouseWheelHandler = function() { var handler = {}, map, _zoomDiv, prevTime, precise = false; function mouseWheel(e) { var delta = 0; prevTime = prevTime || new Date().getTime(); try { _zoomDiv.scrollTop = 1000; _zoomDiv.dispatchEvent(e); delta = 1000 - _zoomDiv.scrollTop; } catch (error) { delta = e.wheelDelta || (-e.detail * 5); } // limit mousewheeling to once every 200ms var timeSince = new Date().getTime() - prevTime; var point = MM.getMousePoint(e, map); if (Math.abs(delta) > 0 && (timeSince > 200) && !precise) { map.zoomByAbout(delta > 0 ? 1 : -1, point); prevTime = new Date().getTime(); } else if (precise) { map.zoomByAbout(delta * 0.001, point); } // Cancel the event so that the page doesn't scroll return MM.cancelEvent(e); } handler.init = function(x) { map = x; _zoomDiv = document.body.appendChild(document.createElement('div')); _zoomDiv.style.cssText = 'visibility:hidden;top:0;height:0;width:0;overflow-y:scroll'; var innerDiv = _zoomDiv.appendChild(document.createElement('div')); innerDiv.style.height = '2000px'; MM.addEvent(map.parent, 'mousewheel', mouseWheel); return handler; }; handler.precise = function(x) { if (!arguments.length) return precise; precise = x; return handler; }; handler.remove = function() { MM.removeEvent(map.parent, 'mousewheel', mouseWheel); _zoomDiv.parentNode.removeChild(_zoomDiv); }; return handler; }; MM.DoubleClickHandler = function() { var handler = {}, map; function doubleClick(e) { // Ensure that this handler is attached once. // Get the point on the map that was double-clicked var point = MM.getMousePoint(e, map); // use shift-double-click to zoom out map.zoomByAbout(e.shiftKey ? -1 : 1, point); return MM.cancelEvent(e); } handler.init = function(x) { map = x; MM.addEvent(map.parent, 'dblclick', doubleClick); return handler; }; handler.remove = function() { MM.removeEvent(map.parent, 'dblclick', doubleClick); }; return handler; }; // Handle the use of mouse dragging to pan the map. MM.DragHandler = function() { var handler = {}, prevMouse, map; function mouseDown(e) { if (e.shiftKey || e.button == 2) return; MM.addEvent(document, 'mouseup', mouseUp); MM.addEvent(document, 'mousemove', mouseMove); prevMouse = new MM.Point(e.clientX, e.clientY); map.parent.style.cursor = 'move'; return MM.cancelEvent(e); } function mouseUp(e) { MM.removeEvent(document, 'mouseup', mouseUp); MM.removeEvent(document, 'mousemove', mouseMove); prevMouse = null; map.parent.style.cursor = ''; return MM.cancelEvent(e); } function mouseMove(e) { if (prevMouse) { map.panBy( e.clientX - prevMouse.x, e.clientY - prevMouse.y); prevMouse.x = e.clientX; prevMouse.y = e.clientY; prevMouse.t = +new Date(); } return MM.cancelEvent(e); } handler.init = function(x) { map = x; MM.addEvent(map.parent, 'mousedown', mouseDown); return handler; }; handler.remove = function() { MM.removeEvent(map.parent, 'mousedown', mouseDown); }; return handler; }; MM.MouseHandler = function() { var handler = {}, map, handlers; handler.init = function(x) { map = x; handlers = [ MM.DragHandler().init(map), MM.DoubleClickHandler().init(map), MM.MouseWheelHandler().init(map) ]; return handler; }; handler.remove = function() { for (var i = 0; i < handlers.length; i++) { handlers[i].remove(); } return handler; }; return handler; }; MM.TouchHandler = function() { var handler = {}, map, maxTapTime = 250, maxTapDistance = 30, maxDoubleTapDelay = 350, locations = {}, taps = [], snapToZoom = true, wasPinching = false, lastPinchCenter = null; function isTouchable () { var el = document.createElement('div'); el.setAttribute('ongesturestart', 'return;'); return (typeof el.ongesturestart === 'function'); } function updateTouches(e) { for (var i = 0; i < e.touches.length; i += 1) { var t = e.touches[i]; if (t.identifier in locations) { var l = locations[t.identifier]; l.x = t.clientX; l.y = t.clientY; l.scale = e.scale; } else { locations[t.identifier] = { scale: e.scale, startPos: { x: t.clientX, y: t.clientY }, x: t.clientX, y: t.clientY, time: new Date().getTime() }; } } } // Test whether touches are from the same source - // whether this is the same touchmove event. function sameTouch (event, touch) { return (event && event.touch) && (touch.identifier == event.touch.identifier); } function touchStart(e) { updateTouches(e); } function touchMove(e) { switch (e.touches.length) { case 1: onPanning(e.touches[0]); break; case 2: onPinching(e); break; } updateTouches(e); return MM.cancelEvent(e); } function touchEnd(e) { var now = new Date().getTime(); // round zoom if we're done pinching if (e.touches.length === 0 && wasPinching) { onPinched(lastPinchCenter); } // Look at each changed touch in turn. for (var i = 0; i < e.changedTouches.length; i += 1) { var t = e.changedTouches[i], loc = locations[t.identifier]; // if we didn't see this one (bug?) // or if it was consumed by pinching already // just skip to the next one if (!loc || loc.wasPinch) { continue; } // we now know we have an event object and a // matching touch that's just ended. Let's see // what kind of event it is based on how long it // lasted and how far it moved. var pos = { x: t.clientX, y: t.clientY }, time = now - loc.time, travel = MM.Point.distance(pos, loc.startPos); if (travel > maxTapDistance) { // we will to assume that the drag has been handled separately } else if (time > maxTapTime) { // close in space, but not in time: a hold pos.end = now; pos.duration = time; onHold(pos); } else { // close in both time and space: a tap pos.time = now; onTap(pos); } } // Weird, sometimes an end event doesn't get thrown // for a touch that nevertheless has disappeared. // Still, this will eventually catch those ids: var validTouchIds = {}; for (var j = 0; j < e.touches.length; j++) { validTouchIds[e.touches[j].identifier] = true; } for (var id in locations) { if (!(id in validTouchIds)) { delete validTouchIds[id]; } } return MM.cancelEvent(e); } function onHold (hold) { // TODO } // Handle a tap event - mainly watch for a doubleTap function onTap(tap) { if (taps.length && (tap.time - taps[0].time) < maxDoubleTapDelay) { onDoubleTap(tap); taps = []; return; } taps = [tap]; } // Handle a double tap by zooming in a single zoom level to a // round zoom. function onDoubleTap(tap) { var z = map.getZoom(), // current zoom tz = Math.round(z) + 1, // target zoom dz = tz - z; // desired delate // zoom in to a round number var p = new MM.Point(tap.x, tap.y); map.zoomByAbout(dz, p); } // Re-transform the actual map parent's CSS transformation function onPanning (touch) { var pos = { x: touch.clientX, y: touch.clientY }, prev = locations[touch.identifier]; map.panBy(pos.x - prev.x, pos.y - prev.y); } function onPinching(e) { // use the first two touches and their previous positions var t0 = e.touches[0], t1 = e.touches[1], p0 = new MM.Point(t0.clientX, t0.clientY), p1 = new MM.Point(t1.clientX, t1.clientY), l0 = locations[t0.identifier], l1 = locations[t1.identifier]; // mark these touches so they aren't used as taps/holds l0.wasPinch = true; l1.wasPinch = true; // scale about the center of these touches var center = MM.Point.interpolate(p0, p1, 0.5); map.zoomByAbout( Math.log(e.scale) / Math.LN2 - Math.log(l0.scale) / Math.LN2, center ); // pan from the previous center of these touches var prevCenter = MM.Point.interpolate(l0, l1, 0.5); map.panBy(center.x - prevCenter.x, center.y - prevCenter.y); wasPinching = true; lastPinchCenter = center; } // When a pinch event ends, round the zoom of the map. function onPinched(p) { // TODO: easing if (snapToZoom) { var z = map.getZoom(), // current zoom tz =Math.round(z); // target zoom map.zoomByAbout(tz - z, p); } wasPinching = false; } handler.init = function(x) { map = x; // Fail early if this isn't a touch device. if (!isTouchable()) return handler; MM.addEvent(map.parent, 'touchstart', touchStart); MM.addEvent(map.parent, 'touchmove', touchMove); MM.addEvent(map.parent, 'touchend', touchEnd); return handler; }; handler.remove = function() { // Fail early if this isn't a touch device. if (!isTouchable()) return handler; MM.removeEvent(map.parent, 'touchstart', touchStart); MM.removeEvent(map.parent, 'touchmove', touchMove); MM.removeEvent(map.parent, 'touchend', touchEnd); return handler; }; return handler; }; // CallbackManager // --------------- // A general-purpose event binding manager used by `Map` // and `RequestManager` // Construct a new CallbackManager, with an list of // supported events. MM.CallbackManager = function(owner, events) { this.owner = owner; this.callbacks = {}; for (var i = 0; i < events.length; i++) { this.callbacks[events[i]] = []; } }; // CallbackManager does simple event management for modestmaps MM.CallbackManager.prototype = { // The element on which callbacks will be triggered. owner: null, // An object of callbacks in the form // // { event: function } callbacks: null, // Add a callback to this object - where the `event` is a string of // the event name and `callback` is a function. addCallback: function(event, callback) { if (typeof(callback) == 'function' && this.callbacks[event]) { this.callbacks[event].push(callback); } }, // Remove a callback. The given function needs to be equal (`===`) to // the callback added in `addCallback`, so named functions should be // used as callbacks. removeCallback: function(event, callback) { if (typeof(callback) == 'function' && this.callbacks[event]) { var cbs = this.callbacks[event], len = cbs.length; for (var i = 0; i < len; i++) { if (cbs[i] === callback) { cbs.splice(i,1); break; } } } }, // Trigger a callback, passing it an object or string from the second // argument. dispatchCallback: function(event, message) { if(this.callbacks[event]) { for (var i = 0; i < this.callbacks[event].length; i += 1) { try { this.callbacks[event][i](this.owner, message); } catch(e) { //console.log(e); // meh } } } } }; // RequestManager // -------------- // an image loading queue MM.RequestManager = function() { // The loading bay is a document fragment to optimize appending, since // the elements within are invisible. See // [this blog post](http://ejohn.org/blog/dom-documentfragments/). this.loadingBay = document.createDocumentFragment(); this.requestsById = {}; this.openRequestCount = 0; this.maxOpenRequests = 4; this.requestQueue = []; this.callbackManager = new MM.CallbackManager(this, [ 'requestcomplete', 'requesterror']); }; MM.RequestManager.prototype = { // DOM element, hidden, for making sure images dispatch complete events loadingBay: null, // all known requests, by ID requestsById: null, // current pending requests requestQueue: null, // current open requests (children of loadingBay) openRequestCount: null, // the number of open requests permitted at one time, clamped down // because of domain-connection limits. maxOpenRequests: null, // for dispatching 'requestcomplete' callbackManager: null, addCallback: function(event, callback) { this.callbackManager.addCallback(event,callback); }, removeCallback: function(event, callback) { this.callbackManager.removeCallback(event,callback); }, dispatchCallback: function(event, message) { this.callbackManager.dispatchCallback(event,message); }, // Clear everything in the queue by excluding nothing clear: function() { this.clearExcept({}); }, clearRequest: function(id) { if(id in this.requestsById) { delete this.requestsById[id]; } for(var i = 0; i < this.requestQueue.length; i++) { var request = this.requestQueue[i]; if(request && request.id == id) { this.requestQueue[i] = null; } } }, // Clear everything in the queue except for certain keys, specified // by an object of the form // // { key: throwawayvalue } clearExcept: function(validIds) { // clear things from the queue first... for (var i = 0; i < this.requestQueue.length; i++) { var request = this.requestQueue[i]; if (request && !(request.id in validIds)) { this.requestQueue[i] = null; } } // then check the loadingBay... var openRequests = this.loadingBay.childNodes; for (var j = openRequests.length-1; j >= 0; j--) { var img = openRequests[j]; if (!(img.id in validIds)) { this.loadingBay.removeChild(img); this.openRequestCount--; /* console.log(this.openRequestCount + " open requests"); */ img.src = img.coord = img.onload = img.onerror = null; } } // hasOwnProperty protects against prototype additions // > "The standard describes an augmentable Object.prototype. // Ignore standards at your own peril." // -- http://www.yuiblog.com/blog/2006/09/26/for-in-intrigue/ for (var id in this.requestsById) { if (!(id in validIds)) { if (this.requestsById.hasOwnProperty(id)) { var requestToRemove = this.requestsById[id]; // whether we've done the request or not... delete this.requestsById[id]; if (requestToRemove !== null) { requestToRemove = requestToRemove.id = requestToRemove.coord = requestToRemove.url = null; } } } } }, // Given a tile id, check whether the RequestManager is currently // requesting it and waiting for the result. hasRequest: function(id) { return (id in this.requestsById); }, // * TODO: remove dependency on coord (it's for sorting, maybe call it data?) // * TODO: rename to requestImage once it's not tile specific requestTile: function(id, coord, url) { if (!(id in this.requestsById)) { var request = { id: id, coord: coord.copy(), url: url }; // if there's no url just make sure we don't request this image again this.requestsById[id] = request; if (url) { this.requestQueue.push(request); /* console.log(this.requestQueue.length + ' pending requests'); */ } } }, getProcessQueue: function() { // let's only create this closure once... if (!this._processQueue) { var theManager = this; this._processQueue = function() { theManager.processQueue(); }; } return this._processQueue; }, // Select images from the `requestQueue` and create image elements for // them, attaching their load events to the function returned by // `this.getLoadComplete()` so that they can be added to the map. processQueue: function(sortFunc) { // When the request queue fills up beyond 8, start sorting the // requests so that spiral-loading or another pattern can be used. if (sortFunc && this.requestQueue.length > 8) { this.requestQueue.sort(sortFunc); } while (this.openRequestCount < this.maxOpenRequests && this.requestQueue.length > 0) { var request = this.requestQueue.pop(); if (request) { this.openRequestCount++; /* console.log(this.openRequestCount + ' open requests'); */ // JSLitmus benchmark shows createElement is a little faster than // new Image() in Firefox and roughly the same in Safari: // http://tinyurl.com/y9wz2jj http://tinyurl.com/yes6rrt var img = document.createElement('img'); // FIXME: id is technically not unique in document if there // are two Maps but toKey is supposed to be fast so we're trying // to avoid a prefix ... hence we can't use any calls to // `document.getElementById()` to retrieve images img.id = request.id; img.style.position = 'absolute'; // * FIXME: store this elsewhere to avoid scary memory leaks? // * FIXME: call this 'data' not 'coord' so that RequestManager is less Tile-centric? img.coord = request.coord; // add it to the DOM in a hidden layer, this is a bit of a hack, but it's // so that the event we get in image.onload has srcElement assigned in IE6 this.loadingBay.appendChild(img); // set these before img.src to avoid missing an img that's already cached img.onload = img.onerror = this.getLoadComplete(); img.src = request.url; // keep things tidy request = request.id = request.coord = request.url = null; } } }, _loadComplete: null, // Get the singleton `_loadComplete` function that is called on image // load events, either removing them from the queue and dispatching an // event to add them to the map, or deleting them if the image failed // to load. getLoadComplete: function() { // let's only create this closure once... if (!this._loadComplete) { var theManager = this; this._loadComplete = function(e) { // this is needed because we don't use MM.addEvent for images e = e || window.event; // srcElement for IE, target for FF, Safari etc. var img = e.srcElement || e.target; // unset these straight away so we don't call this twice img.onload = img.onerror = null; // pull it back out of the (hidden) DOM // so that draw will add it correctly later theManager.loadingBay.removeChild(img); theManager.openRequestCount--; delete theManager.requestsById[img.id]; /* console.log(theManager.openRequestCount + ' open requests'); */ // NB:- complete is also true onerror if we got a 404 if (e.type === 'load' && (img.complete || (img.readyState && img.readyState == 'complete'))) { theManager.dispatchCallback('requestcomplete', img); } else { // if it didn't finish clear its src to make sure it // really stops loading // FIXME: we'll never retry because this id is still // in requestsById - is that right? theManager.dispatchCallback('requesterror', { element: img, url: ('' + img.src) }); img.src = null; } // keep going in the same order // use `setTimeout()` to avoid the IE recursion limit, see // http://cappuccino.org/discuss/2010/03/01/internet-explorer-global-variables-and-stack-overflows/ // and https://github.com/stamen/modestmaps-js/issues/12 setTimeout(theManager.getProcessQueue(), 0); }; } return this._loadComplete; } }; // Layer MM.Layer = function(provider, parent, name) { this.parent = parent || document.createElement('div'); this.parent.style.cssText = 'position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; margin: 0; padding: 0; z-index: 0'; this.name = name; this.levels = {}; this.requestManager = new MM.RequestManager(); this.requestManager.addCallback('requestcomplete', this.getTileComplete()); this.requestManager.addCallback('requesterror', this.getTileError()); if (provider) this.setProvider(provider); }; MM.Layer.prototype = { map: null, // TODO: remove parent: null, name: null, enabled: true, tiles: null, levels: null, requestManager: null, provider: null, _tileComplete: null, getTileComplete: function() { if (!this._tileComplete) { var theLayer = this; this._tileComplete = function(manager, tile) { theLayer.tiles[tile.id] = tile; theLayer.positionTile(tile); }; } return this._tileComplete; }, getTileError: function() { if (!this._tileError) { var theLayer = this; this._tileError = function(manager, tile) { tile.element.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'; theLayer.tiles[tile.element.id] = tile.element; theLayer.positionTile(tile.element); }; } return this._tileError; }, draw: function() { if (!this.enabled || !this.map) return; // compares manhattan distance from center of // requested tiles to current map center // NB:- requested tiles are *popped* from queue, so we do a descending sort var theCoord = this.map.coordinate.zoomTo(Math.round(this.map.coordinate.zoom)); function centerDistanceCompare(r1, r2) { if (r1 && r2) { var c1 = r1.coord; var c2 = r2.coord; if (c1.zoom == c2.zoom) { var ds1 = Math.abs(theCoord.row - c1.row - 0.5) + Math.abs(theCoord.column - c1.column - 0.5); var ds2 = Math.abs(theCoord.row - c2.row - 0.5) + Math.abs(theCoord.column - c2.column - 0.5); return ds1 < ds2 ? 1 : ds1 > ds2 ? -1 : 0; } else { return c1.zoom < c2.zoom ? 1 : c1.zoom > c2.zoom ? -1 : 0; } } return r1 ? 1 : r2 ? -1 : 0; } // if we're in between zoom levels, we need to choose the nearest: var baseZoom = Math.round(this.map.coordinate.zoom); // these are the top left and bottom right tile coordinates // we'll be loading everything in between: var startCoord = this.map.pointCoordinate(new MM.Point(0,0)) .zoomTo(baseZoom).container(); var endCoord = this.map.pointCoordinate(this.map.dimensions) .zoomTo(baseZoom).container().right().down(); // tiles with invalid keys will be removed from visible levels // requests for tiles with invalid keys will be canceled // (this object maps from a tile key to a boolean) var validTileKeys = { }; // make sure we have a container for tiles in the current level var levelElement = this.createOrGetLevel(startCoord.zoom); // use this coordinate for generating keys, parents and children: var tileCoord = startCoord.copy(); for (tileCoord.column = startCoord.column; tileCoord.column <= endCoord.column; tileCoord.column++) { for (tileCoord.row = startCoord.row; tileCoord.row <= endCoord.row; tileCoord.row++) { var validKeys = this.inventoryVisibleTile(levelElement, tileCoord); while (validKeys.length) { validTileKeys[validKeys.pop()] = true; } } } // i from i to zoom-5 are levels that would be scaled too big, // i from zoom + 2 to levels. length are levels that would be // scaled too small (and tiles would be too numerous) for (var name in this.levels) { if (this.levels.hasOwnProperty(name)) { var zoom = parseInt(name,10); if (zoom >= startCoord.zoom - 5 && zoom < startCoord.zoom + 2) { continue; } var level = this.levels[name]; level.style.display = 'none'; var visibleTiles = this.tileElementsInLevel(level); while (visibleTiles.length) { this.provider.releaseTile(visibleTiles[0].coord); this.requestManager.clearRequest(visibleTiles[0].coord.toKey()); level.removeChild(visibleTiles[0]); visibleTiles.shift(); } } } // levels we want to see, if they have tiles in validTileKeys var minLevel = startCoord.zoom - 5; var maxLevel = startCoord.zoom + 2; for (var z = minLevel; z < maxLevel; z++) { this.adjustVisibleLevel(this.levels[z], z, validTileKeys); } // cancel requests that aren't visible: this.requestManager.clearExcept(validTileKeys); // get newly requested tiles, sort according to current view: this.requestManager.processQueue(centerDistanceCompare); }, // For a given tile coordinate in a given level element, ensure that it's // correctly represented in the DOM including potentially-overlapping // parent and child tiles for pyramid loading. // // Return a list of valid (i.e. loadable?) tile keys. inventoryVisibleTile: function(layer_element, tile_coord) { var tile_key = tile_coord.toKey(), valid_tile_keys = [tile_key]; // Check that the needed tile already exists someplace - add it to the DOM if it does. if (tile_key in this.tiles) { var tile = this.tiles[tile_key]; // ensure it's in the DOM: if (tile.parentNode != layer_element) { layer_element.appendChild(tile); // if the provider implements reAddTile(), call it if ("reAddTile" in this.provider) { this.provider.reAddTile(tile_key, tile_coord, tile); } } return valid_tile_keys; } // Check that the needed tile has even been requested at all. if (!this.requestManager.hasRequest(tile_key)) { var tileToRequest = this.provider.getTile(tile_coord); if (typeof tileToRequest == 'string') { this.addTileImage(tile_key, tile_coord, tileToRequest); // tile must be truish } else if (tileToRequest) { this.addTileElement(tile_key, tile_coord, tileToRequest); } } // look for a parent tile in our image cache var tileCovered = false; var maxStepsOut = tile_coord.zoom; for (var pz = 1; pz <= maxStepsOut; pz++) { var parent_coord = tile_coord.zoomBy(-pz).container(); var parent_key = parent_coord.toKey(); // only mark it valid if we have it already if (parent_key in this.tiles) { valid_tile_keys.push(parent_key); tileCovered = true; break; } } // if we didn't find a parent, look at the children: if (!tileCovered) { var child_coord = tile_coord.zoomBy(1); // mark everything valid whether or not we have it: valid_tile_keys.push(child_coord.toKey()); child_coord.column += 1; valid_tile_keys.push(child_coord.toKey()); child_coord.row += 1; valid_tile_keys.push(child_coord.toKey()); child_coord.column -= 1; valid_tile_keys.push(child_coord.toKey()); } return valid_tile_keys; }, tileElementsInLevel: function(level) { // this is somewhat future proof, we're looking for DOM elements // not necessarily elements var tiles = []; for (var tile = level.firstChild; tile; tile = tile.nextSibling) { if (tile.nodeType == 1) { tiles.push(tile); } } return tiles; }, /** * For a given level, adjust visibility as a whole and discard individual * tiles based on values in valid_tile_keys from inventoryVisibleTile(). */ adjustVisibleLevel: function(level, zoom, valid_tile_keys) { // no tiles for this level yet if (!level) return; var scale = 1; var theCoord = this.map.coordinate.copy(); if (level.childNodes.length > 0) { level.style.display = 'block'; scale = Math.pow(2, this.map.coordinate.zoom - zoom); theCoord = theCoord.zoomTo(zoom); } else { level.style.display = 'none'; return false; } var tileWidth = this.map.tileSize.x * scale; var tileHeight = this.map.tileSize.y * scale; var center = new MM.Point(this.map.dimensions.x/2, this.map.dimensions.y/2); var tiles = this.tileElementsInLevel(level); while (tiles.length) { var tile = tiles.pop(); if (!valid_tile_keys[tile.id]) { this.provider.releaseTile(tile.coord); this.requestManager.clearRequest(tile.coord.toKey()); level.removeChild(tile); } else { // position tiles MM.moveElement(tile, { x: Math.round(center.x + (tile.coord.column - theCoord.column) * tileWidth), y: Math.round(center.y + (tile.coord.row - theCoord.row) * tileHeight), scale: scale, // TODO: pass only scale or only w/h width: this.map.tileSize.x, height: this.map.tileSize.y }); } } }, createOrGetLevel: function(zoom) { if (zoom in this.levels) { return this.levels[zoom]; } var level = document.createElement('div'); level.id = this.parent.id + '-zoom-' + zoom; level.style.cssText = 'position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; margin: 0; padding: 0;'; level.style.zIndex = zoom; this.parent.appendChild(level); this.levels[zoom] = level; return level; }, addTileImage: function(key, coord, url) { this.requestManager.requestTile(key, coord, url); }, addTileElement: function(key, coordinate, element) { // Expected in draw() element.id = key; element.coord = coordinate.copy(); this.positionTile(element); }, positionTile: function(tile) { // position this tile (avoids a full draw() call): var theCoord = this.map.coordinate.zoomTo(tile.coord.zoom); // Start tile positioning and prevent drag for modern browsers tile.style.cssText = 'position:absolute;-webkit-user-select:none;' + '-webkit-user-drag:none;-moz-user-drag:none;-webkit-transform-origin:0 0;' + '-moz-transform-origin:0 0;-o-transform-origin:0 0;-ms-transform-origin:0 0;' + 'width:' + this.map.tileSize.x + 'px; height: ' + this.map.tileSize.y + 'px;'; // Prevent drag for IE tile.ondragstart = function() { return false; }; var scale = Math.pow(2, this.map.coordinate.zoom - tile.coord.zoom); MM.moveElement(tile, { x: Math.round((this.map.dimensions.x/2) + (tile.coord.column - theCoord.column) * this.map.tileSize.x), y: Math.round((this.map.dimensions.y/2) + (tile.coord.row - theCoord.row) * this.map.tileSize.y), scale: scale, // TODO: pass only scale or only w/h width: this.map.tileSize.x, height: this.map.tileSize.y }); // add tile to its level var theLevel = this.levels[tile.coord.zoom]; theLevel.appendChild(tile); // Support style transition if available. tile.className = 'map-tile-loaded'; // ensure the level is visible if it's still the current level if (Math.round(this.map.coordinate.zoom) == tile.coord.zoom) { theLevel.style.display = 'block'; } // request a lazy redraw of all levels // this will remove tiles that were only visible // to cover this tile while it loaded: this.requestRedraw(); }, _redrawTimer: undefined, requestRedraw: function() { // we'll always draw within 1 second of this request, // sometimes faster if there's already a pending redraw // this is used when a new tile arrives so that we clear // any parent/child tiles that were only being displayed // until the tile loads at the right zoom level if (!this._redrawTimer) { this._redrawTimer = setTimeout(this.getRedraw(), 1000); } }, _redraw: null, getRedraw: function() { // let's only create this closure once... if (!this._redraw) { var theLayer = this; this._redraw = function() { theLayer.draw(); theLayer._redrawTimer = 0; }; } return this._redraw; }, setProvider: function(newProvider) { var firstProvider = (this.provider === null); // if we already have a provider the we'll need to // clear the DOM, cancel requests and redraw if (!firstProvider) { this.requestManager.clear(); for (var name in this.levels) { if (this.levels.hasOwnProperty(name)) { var level = this.levels[name]; while (level.firstChild) { this.provider.releaseTile(level.firstChild.coord); level.removeChild(level.firstChild); } } } } // first provider or not we'll init/reset some values... this.tiles = {}; // for later: check geometry of old provider and set a new coordinate center // if needed (now? or when?) this.provider = newProvider; if (!firstProvider) { this.draw(); } }, // Enable a layer and show its dom element enable: function() { this.enabled = true; this.parent.style.display = ''; this.draw(); return this; }, // Disable a layer, don't display in DOM, clear all requests disable: function() { this.enabled = false; this.requestManager.clear(); this.parent.style.display = 'none'; return this; }, // Remove this layer from the DOM, cancel all of its requests // and unbind any callbacks that are bound to it. destroy: function() { this.requestManager.clear(); this.requestManager.removeCallback('requestcomplete', this.getTileComplete()); this.requestManager.removeCallback('requesterror', this.getTileError()); // TODO: does requestManager need a destroy function too? this.provider = null; // If this layer was ever attached to the DOM, detach it. if (this.parent.parentNode) { this.parent.parentNode.removeChild(this.parent); } this.map = null; } }; // Map // Instance of a map intended for drawing to a div. // // * `parent` (required DOM element) // Can also be an ID of a DOM element // * `layerOrLayers` (required MM.Layer or Array of MM.Layers) // each one must implement draw(), destroy(), have a .parent DOM element and a .map property // (an array of URL templates or MM.MapProviders is also acceptable) // * `dimensions` (optional Point) // Size of map to create // * `eventHandlers` (optional Array) // If empty or null MouseHandler will be used // Otherwise, each handler will be called with init(map) MM.Map = function(parent, layerOrLayers, dimensions, eventHandlers) { if (typeof parent == 'string') { parent = document.getElementById(parent); if (!parent) { throw 'The ID provided to modest maps could not be found.'; } } this.parent = parent; // we're no longer adding width and height to parent.style but we still // need to enforce padding, overflow and position otherwise everything screws up // TODO: maybe console.warn if the current values are bad? this.parent.style.padding = '0'; this.parent.style.overflow = 'hidden'; var position = MM.getStyle(this.parent, 'position'); if (position != 'relative' && position != 'absolute') { this.parent.style.position = 'relative'; } this.layers = []; if (!layerOrLayers) { layerOrLayers = []; } if (!(layerOrLayers instanceof Array)) { layerOrLayers = [ layerOrLayers ]; } for (var i = 0; i < layerOrLayers.length; i++) { this.addLayer(layerOrLayers[i]); } // default to Google-y Mercator style maps this.projection = new MM.MercatorProjection(0, MM.deriveTransformation(-Math.PI, Math.PI, 0, 0, Math.PI, Math.PI, 1, 0, -Math.PI, -Math.PI, 0, 1)); this.tileSize = new MM.Point(256, 256); // default 0-18 zoom level // with infinite horizontal pan and clamped vertical pan this.coordLimits = [ new MM.Coordinate(0,-Infinity,0), // top left outer new MM.Coordinate(1,Infinity,0).zoomTo(18) // bottom right inner ]; // eyes towards null island this.coordinate = new MM.Coordinate(0.5, 0.5, 0); // if you don't specify dimensions we assume you want to fill the parent // unless the parent has no w/h, in which case we'll still use a default if (!dimensions) { dimensions = new MM.Point(this.parent.offsetWidth, this.parent.offsetHeight); this.autoSize = true; // use destroy to get rid of this handler from the DOM MM.addEvent(window, 'resize', this.windowResize()); } else { this.autoSize = false; // don't call setSize here because it calls draw() this.parent.style.width = Math.round(dimensions.x) + 'px'; this.parent.style.height = Math.round(dimensions.y) + 'px'; } this.dimensions = dimensions; this.callbackManager = new MM.CallbackManager(this, [ 'zoomed', 'panned', 'centered', 'extentset', 'resized', 'drawn' ]); // set up handlers last so that all required attributes/functions are in place if needed if (eventHandlers === undefined) { this.eventHandlers = [ MM.MouseHandler().init(this), MM.TouchHandler().init(this) ]; } else { this.eventHandlers = eventHandlers; if (eventHandlers instanceof Array) { for (var j = 0; j < eventHandlers.length; j++) { eventHandlers[j].init(this); } } } }; MM.Map.prototype = { parent: null, // DOM Element dimensions: null, // MM.Point with x/y size of parent element projection: null, // MM.Projection of first known layer coordinate: null, // Center of map MM.Coordinate with row/column/zoom tileSize: null, // MM.Point with x/y size of tiles coordLimits: null, // Array of [ topLeftOuter, bottomLeftInner ] MM.Coordinates layers: null, // Array of MM.Layer (interface = .draw(), .destroy(), .parent and .map) callbackManager: null, // MM.CallbackManager, handles map events eventHandlers: null, // Array of interaction handlers, just a MM.MouseHandler by default autoSize: null, // Boolean, true if we have a window resize listener toString: function() { return 'Map(#' + this.parent.id + ')'; }, // callbacks... addCallback: function(event, callback) { this.callbackManager.addCallback(event, callback); return this; }, removeCallback: function(event, callback) { this.callbackManager.removeCallback(event, callback); return this; }, dispatchCallback: function(event, message) { this.callbackManager.dispatchCallback(event, message); return this; }, windowResize: function() { if (!this._windowResize) { var theMap = this; this._windowResize = function(event) { // don't call setSize here because it sets parent.style.width/height // and setting the height breaks percentages and default styles theMap.dimensions = new MM.Point(theMap.parent.offsetWidth, theMap.parent.offsetHeight); theMap.draw(); theMap.dispatchCallback('resized', [theMap.dimensions]); }; } return this._windowResize; }, // A convenience function to restrict interactive zoom ranges. // (you should also adjust map provider to restrict which tiles get loaded, // or modify map.coordLimits and provider.tileLimits for finer control) setZoomRange: function(minZoom, maxZoom) { this.coordLimits[0] = this.coordLimits[0].zoomTo(minZoom); this.coordLimits[1] = this.coordLimits[1].zoomTo(maxZoom); return this; }, // zooming zoomBy: function(zoomOffset) { this.coordinate = this.enforceLimits(this.coordinate.zoomBy(zoomOffset)); MM.getFrame(this.getRedraw()); this.dispatchCallback('zoomed', zoomOffset); return this; }, zoomIn: function() { return this.zoomBy(1); }, zoomOut: function() { return this.zoomBy(-1); }, setZoom: function(z) { return this.zoomBy(z - this.coordinate.zoom); }, zoomByAbout: function(zoomOffset, point) { var location = this.pointLocation(point); this.coordinate = this.enforceLimits(this.coordinate.zoomBy(zoomOffset)); var newPoint = this.locationPoint(location); this.dispatchCallback('zoomed', zoomOffset); return this.panBy(point.x - newPoint.x, point.y - newPoint.y); }, // panning panBy: function(dx, dy) { this.coordinate.column -= dx / this.tileSize.x; this.coordinate.row -= dy / this.tileSize.y; this.coordinate = this.enforceLimits(this.coordinate); // Defer until the browser is ready to draw. MM.getFrame(this.getRedraw()); this.dispatchCallback('panned', [dx, dy]); return this; }, panLeft: function() { return this.panBy(100, 0); }, panRight: function() { return this.panBy(-100, 0); }, panDown: function() { return this.panBy(0, -100); }, panUp: function() { return this.panBy(0, 100); }, // positioning setCenter: function(location) { return this.setCenterZoom(location, this.coordinate.zoom); }, setCenterZoom: function(location, zoom) { this.coordinate = this.projection.locationCoordinate(location).zoomTo(parseFloat(zoom) || 0); this.coordinate = this.enforceLimits(this.coordinate); MM.getFrame(this.getRedraw()); this.dispatchCallback('centered', [location, zoom]); return this; }, extentCoordinate: function(locations, precise) { // coerce locations to an array if it's a Extent instance if (locations instanceof MM.Extent) { locations = locations.toArray(); } var TL, BR; for (var i = 0; i < locations.length; i++) { var coordinate = this.projection.locationCoordinate(locations[i]); if (TL) { TL.row = Math.min(TL.row, coordinate.row); TL.column = Math.min(TL.column, coordinate.column); TL.zoom = Math.min(TL.zoom, coordinate.zoom); BR.row = Math.max(BR.row, coordinate.row); BR.column = Math.max(BR.column, coordinate.column); BR.zoom = Math.max(BR.zoom, coordinate.zoom); } else { TL = coordinate.copy(); BR = coordinate.copy(); } } var width = this.dimensions.x + 1; var height = this.dimensions.y + 1; // multiplication factor between horizontal span and map width var hFactor = (BR.column - TL.column) / (width / this.tileSize.x); // multiplication factor expressed as base-2 logarithm, for zoom difference var hZoomDiff = Math.log(hFactor) / Math.log(2); // possible horizontal zoom to fit geographical extent in map width var hPossibleZoom = TL.zoom - (precise ? hZoomDiff : Math.ceil(hZoomDiff)); // multiplication factor between vertical span and map height var vFactor = (BR.row - TL.row) / (height / this.tileSize.y); // multiplication factor expressed as base-2 logarithm, for zoom difference var vZoomDiff = Math.log(vFactor) / Math.log(2); // possible vertical zoom to fit geographical extent in map height var vPossibleZoom = TL.zoom - (precise ? vZoomDiff : Math.ceil(vZoomDiff)); // initial zoom to fit extent vertically and horizontally var initZoom = Math.min(hPossibleZoom, vPossibleZoom); // additionally, make sure it's not outside the boundaries set by map limits initZoom = Math.min(initZoom, this.coordLimits[1].zoom); initZoom = Math.max(initZoom, this.coordLimits[0].zoom); // coordinate of extent center var centerRow = (TL.row + BR.row) / 2; var centerColumn = (TL.column + BR.column) / 2; var centerZoom = TL.zoom; return new MM.Coordinate(centerRow, centerColumn, centerZoom).zoomTo(initZoom); }, setExtent: function(locations, precise) { this.coordinate = this.extentCoordinate(locations, precise); this.coordinate = this.enforceLimits(this.coordinate); MM.getFrame(this.getRedraw()); this.dispatchCallback('extentset', locations); return this; }, // Resize the map's container `
`, redrawing the map and triggering // `resized` to make sure that the map's presentation is still correct. setSize: function(dimensions) { // Ensure that, whether a raw object or a Point object is passed, // this.dimensions will be a Point. this.dimensions = new MM.Point(dimensions.x, dimensions.y); this.parent.style.width = Math.round(this.dimensions.x) + 'px'; this.parent.style.height = Math.round(this.dimensions.y) + 'px'; if (this.autoSize) { MM.removeEvent(window, 'resize', this.windowResize()); this.autoSize = false; } this.draw(); // draw calls enforceLimits // (if you switch to getFrame, call enforceLimits first) this.dispatchCallback('resized', this.dimensions); return this; }, // projecting points on and off screen coordinatePoint: function(coord) { // Return an x, y point on the map image for a given coordinate. if (coord.zoom != this.coordinate.zoom) { coord = coord.zoomTo(this.coordinate.zoom); } // distance from the center of the map var point = new MM.Point(this.dimensions.x / 2, this.dimensions.y / 2); point.x += this.tileSize.x * (coord.column - this.coordinate.column); point.y += this.tileSize.y * (coord.row - this.coordinate.row); return point; }, // Get a `MM.Coordinate` from an `MM.Point` - returns a new tile-like object // from a screen point. pointCoordinate: function(point) { // new point coordinate reflecting distance from map center, in tile widths var coord = this.coordinate.copy(); coord.column += (point.x - this.dimensions.x / 2) / this.tileSize.x; coord.row += (point.y - this.dimensions.y / 2) / this.tileSize.y; return coord; }, // Return an MM.Coordinate (row,col,zoom) for an MM.Location (lat,lon). locationCoordinate: function(location) { return this.projection.locationCoordinate(location); }, // Return an MM.Location (lat,lon) for an MM.Coordinate (row,col,zoom). coordinateLocation: function(coordinate) { return this.projection.coordinateLocation(coordinate); }, // Return an x, y point on the map image for a given geographical location. locationPoint: function(location) { return this.coordinatePoint(this.locationCoordinate(location)); }, // Return a geographical location on the map image for a given x, y point. pointLocation: function(point) { return this.coordinateLocation(this.pointCoordinate(point)); }, // inspecting getExtent: function() { return new MM.Extent( this.pointLocation(new MM.Point(0, 0)), this.pointLocation(this.dimensions) ); }, extent: function(locations, precise) { if (locations) { return this.setExtent(locations, precise); } else { return this.getExtent(); } }, // Get the current centerpoint of the map, returning a `Location` getCenter: function() { return this.projection.coordinateLocation(this.coordinate); }, center: function(location) { if (location) { return this.setCenter(location); } else { return this.getCenter(); } }, // Get the current zoom level of the map, returning a number getZoom: function() { return this.coordinate.zoom; }, zoom: function(zoom) { if (zoom !== undefined) { return this.setZoom(zoom); } else { return this.getZoom(); } }, // return a copy of the layers array getLayers: function() { return this.layers.slice(); }, // return the first layer with given name getLayer: function(name) { for (var i = 0; i < this.layers.length; i++) { if (name == this.layers[i].name) return this.layers[i]; } }, // return the layer at the given index getLayerAt: function(index) { return this.layers[index]; }, // put the given layer on top of all the others // Since this is called for the first layer, which is by definition // added before the map has a valid `coordinate`, we request // a redraw only if the map has a center coordinate. addLayer: function(layer) { this.layers.push(layer); this.parent.appendChild(layer.parent); layer.map = this; // TODO: remove map property from MM.Layer? if (this.coordinate) { MM.getFrame(this.getRedraw()); } return this; }, // find the given layer and remove it removeLayer: function(layer) { for (var i = 0; i < this.layers.length; i++) { if (layer == this.layers[i] || layer == this.layers[i].name) { this.removeLayerAt(i); break; } } return this; }, // replace the current layer at the given index with the given layer setLayerAt: function(index, layer) { if (index < 0 || index >= this.layers.length) { throw new Error('invalid index in setLayerAt(): ' + index); } if (this.layers[index] != layer) { // clear existing layer at this index if (index < this.layers.length) { var other = this.layers[index]; this.parent.insertBefore(layer.parent, other.parent); other.destroy(); } else { // Or if this will be the last layer, it can be simply appended this.parent.appendChild(layer.parent); } this.layers[index] = layer; layer.map = this; // TODO: remove map property from MM.Layer MM.getFrame(this.getRedraw()); } return this; }, // put the given layer at the given index, moving others if necessary insertLayerAt: function(index, layer) { if (index < 0 || index > this.layers.length) { throw new Error('invalid index in insertLayerAt(): ' + index); } if (index == this.layers.length) { // it just gets tacked on to the end this.layers.push(layer); this.parent.appendChild(layer.parent); } else { // it needs to get slipped in amongst the others var other = this.layers[index]; this.parent.insertBefore(layer.parent, other.parent); this.layers.splice(index, 0, layer); } layer.map = this; // TODO: remove map property from MM.Layer MM.getFrame(this.getRedraw()); return this; }, // remove the layer at the given index, call .destroy() on the layer removeLayerAt: function(index) { if (index < 0 || index >= this.layers.length) { throw new Error('invalid index in removeLayer(): ' + index); } // gone baby gone. var old = this.layers[index]; this.layers.splice(index, 1); old.destroy(); return this; }, // switch the stacking order of two layers, by index swapLayersAt: function(i, j) { if (i < 0 || i >= this.layers.length || j < 0 || j >= this.layers.length) { throw new Error('invalid index in swapLayersAt(): ' + index); } var layer1 = this.layers[i], layer2 = this.layers[j], dummy = document.createElement('div'); // kick layer2 out, replace it with the dummy. this.parent.replaceChild(dummy, layer2.parent); // put layer2 back in and kick layer1 out this.parent.replaceChild(layer2.parent, layer1.parent); // put layer1 back in and ditch the dummy this.parent.replaceChild(layer1.parent, dummy); // now do it to the layers array this.layers[i] = layer2; this.layers[j] = layer1; return this; }, // Enable and disable layers. // Disabled layers are not displayed, are not drawn, and do not request // tiles. They do maintain their layer index on the map. enableLayer: function(name) { var l = this.getLayer(name); if (l) l.enable(); return this; }, enableLayerAt: function(index) { var l = this.getLayerAt(index); if (l) l.enable(); return this; }, disableLayer: function(name) { var l = this.getLayer(name); if (l) l.disable(); return this; }, disableLayerAt: function(index) { var l = this.getLayerAt(index); if (l) l.disable(); return this; }, // limits enforceZoomLimits: function(coord) { var limits = this.coordLimits; if (limits) { // clamp zoom level: var minZoom = limits[0].zoom; var maxZoom = limits[1].zoom; if (coord.zoom < minZoom) { coord = coord.zoomTo(minZoom); } else if (coord.zoom > maxZoom) { coord = coord.zoomTo(maxZoom); } } return coord; }, enforcePanLimits: function(coord) { if (this.coordLimits) { coord = coord.copy(); // clamp pan: var topLeftLimit = this.coordLimits[0].zoomTo(coord.zoom); var bottomRightLimit = this.coordLimits[1].zoomTo(coord.zoom); var currentTopLeft = this.pointCoordinate(new MM.Point(0, 0)) .zoomTo(coord.zoom); var currentBottomRight = this.pointCoordinate(this.dimensions) .zoomTo(coord.zoom); // this handles infinite limits: // (Infinity - Infinity) is Nan // NaN is never less than anything if (bottomRightLimit.row - topLeftLimit.row < currentBottomRight.row - currentTopLeft.row) { // if the limit is smaller than the current view center it coord.row = (bottomRightLimit.row + topLeftLimit.row) / 2; } else { if (currentTopLeft.row < topLeftLimit.row) { coord.row += topLeftLimit.row - currentTopLeft.row; } else if (currentBottomRight.row > bottomRightLimit.row) { coord.row -= currentBottomRight.row - bottomRightLimit.row; } } if (bottomRightLimit.column - topLeftLimit.column < currentBottomRight.column - currentTopLeft.column) { // if the limit is smaller than the current view, center it coord.column = (bottomRightLimit.column + topLeftLimit.column) / 2; } else { if (currentTopLeft.column < topLeftLimit.column) { coord.column += topLeftLimit.column - currentTopLeft.column; } else if (currentBottomRight.column > bottomRightLimit.column) { coord.column -= currentBottomRight.column - bottomRightLimit.column; } } } return coord; }, // Prevent accidentally navigating outside the `coordLimits` of the map. enforceLimits: function(coord) { return this.enforcePanLimits(this.enforceZoomLimits(coord)); }, // rendering // Redraw the tiles on the map, reusing existing tiles. draw: function() { // make sure we're not too far in or out: this.coordinate = this.enforceLimits(this.coordinate); // if we don't have dimensions, check the parent size if (this.dimensions.x <= 0 || this.dimensions.y <= 0) { if (this.autoSize) { // maybe the parent size has changed? var w = this.parent.offsetWidth, h = this.parent.offsetHeight; this.dimensions = new MM.Point(w,h); if (w <= 0 || h <= 0) { return; } } else { // the issue can only be corrected with setSize return; } } // draw layers one by one for(var i = 0; i < this.layers.length; i++) { this.layers[i].draw(); } this.dispatchCallback('drawn'); }, _redrawTimer: undefined, requestRedraw: function() { // we'll always draw within 1 second of this request, // sometimes faster if there's already a pending redraw // this is used when a new tile arrives so that we clear // any parent/child tiles that were only being displayed // until the tile loads at the right zoom level if (!this._redrawTimer) { this._redrawTimer = setTimeout(this.getRedraw(), 1000); } }, _redraw: null, getRedraw: function() { // let's only create this closure once... if (!this._redraw) { var theMap = this; this._redraw = function() { theMap.draw(); theMap._redrawTimer = 0; }; } return this._redraw; }, // Attempts to destroy all attachment a map has to a page // and clear its memory usage. destroy: function() { for (var j = 0; j < this.layers.length; j++) { this.layers[j].destroy(); } this.layers = []; this.projection = null; for (var i = 0; i < this.eventHandlers.length; i++) { this.eventHandlers[i].remove(); } if (this.autoSize) { MM.removeEvent(window, 'resize', this.windowResize()); } } }; // Instance of a map intended for drawing to a div. // // * `parent` (required DOM element) // Can also be an ID of a DOM element // * `provider` (required MM.MapProvider or URL template) // * `location` (required MM.Location) // Location for map to show // * `zoom` (required number) MM.mapByCenterZoom = function(parent, layerish, location, zoom) { var layer = MM.coerceLayer(layerish), map = new MM.Map(parent, layer, false); map.setCenterZoom(location, zoom).draw(); return map; }; // Instance of a map intended for drawing to a div. // // * `parent` (required DOM element) // Can also be an ID of a DOM element // * `provider` (required MM.MapProvider or URL template) // * `locationA` (required MM.Location) // Location of one map corner // * `locationB` (required MM.Location) // Location of other map corner MM.mapByExtent = function(parent, layerish, locationA, locationB) { var layer = MM.coerceLayer(layerish), map = new MM.Map(parent, layer, false); map.setExtent([locationA, locationB]).draw(); return map; }; if (typeof module !== 'undefined' && module.exports) { module.exports = { Point: MM.Point, Projection: MM.Projection, MercatorProjection: MM.MercatorProjection, LinearProjection: MM.LinearProjection, Transformation: MM.Transformation, Location: MM.Location, MapProvider: MM.MapProvider, Template: MM.Template, Coordinate: MM.Coordinate, deriveTransformation: MM.deriveTransformation }; } })(MM); modestmaps-js-3.3.6/package.json000066400000000000000000000013401210227260500165720ustar00rootroot00000000000000{ "name": "modestmaps", "description": "a display and interaction library for tile-based maps", "version": "3.3.6", "author": { "name": "Tom Carden", "email": "tom@tom-carden.co.uk", "url": "http://www.tom-carden.co.uk/" }, "contributors": [ "Mike Migurski ", "Shawn Allen ", "Tom MacWright " ], "keywords": ["map", "geo", "browser"], "main": "./modestmaps.js", "homepage": "https://github.com/modestmaps/modestmaps-js", "repositories": [{ "type" : "git", "url" : "git://github.com/modestmaps/modestmaps-js.git" }], "devDependencies": { "docco": "~0.3.0", "uglify-js": "~1.0.0" }, "directories": { } } modestmaps-js-3.3.6/src/000077500000000000000000000000001210227260500150755ustar00rootroot00000000000000modestmaps-js-3.3.6/src/callbacks.js000066400000000000000000000043001210227260500173470ustar00rootroot00000000000000 // CallbackManager // --------------- // A general-purpose event binding manager used by `Map` // and `RequestManager` // Construct a new CallbackManager, with an list of // supported events. MM.CallbackManager = function(owner, events) { this.owner = owner; this.callbacks = {}; for (var i = 0; i < events.length; i++) { this.callbacks[events[i]] = []; } }; // CallbackManager does simple event management for modestmaps MM.CallbackManager.prototype = { // The element on which callbacks will be triggered. owner: null, // An object of callbacks in the form // // { event: function } callbacks: null, // Add a callback to this object - where the `event` is a string of // the event name and `callback` is a function. addCallback: function(event, callback) { if (typeof(callback) == 'function' && this.callbacks[event]) { this.callbacks[event].push(callback); } }, // Remove a callback. The given function needs to be equal (`===`) to // the callback added in `addCallback`, so named functions should be // used as callbacks. removeCallback: function(event, callback) { if (typeof(callback) == 'function' && this.callbacks[event]) { var cbs = this.callbacks[event], len = cbs.length; for (var i = 0; i < len; i++) { if (cbs[i] === callback) { cbs.splice(i,1); break; } } } }, // Trigger a callback, passing it an object or string from the second // argument. dispatchCallback: function(event, message) { if(this.callbacks[event]) { for (var i = 0; i < this.callbacks[event].length; i += 1) { try { this.callbacks[event][i](this.owner, message); } catch(e) { //console.log(e); // meh } } } } }; modestmaps-js-3.3.6/src/convenience.js000066400000000000000000000022751210227260500177350ustar00rootroot00000000000000 // Instance of a map intended for drawing to a div. // // * `parent` (required DOM element) // Can also be an ID of a DOM element // * `provider` (required MM.MapProvider or URL template) // * `location` (required MM.Location) // Location for map to show // * `zoom` (required number) MM.mapByCenterZoom = function(parent, layerish, location, zoom) { var layer = MM.coerceLayer(layerish), map = new MM.Map(parent, layer, false); map.setCenterZoom(location, zoom).draw(); return map; }; // Instance of a map intended for drawing to a div. // // * `parent` (required DOM element) // Can also be an ID of a DOM element // * `provider` (required MM.MapProvider or URL template) // * `locationA` (required MM.Location) // Location of one map corner // * `locationB` (required MM.Location) // Location of other map corner MM.mapByExtent = function(parent, layerish, locationA, locationB) { var layer = MM.coerceLayer(layerish), map = new MM.Map(parent, layer, false); map.setExtent([locationA, locationB]).draw(); return map; }; modestmaps-js-3.3.6/src/coordinate.js000066400000000000000000000065351210227260500175730ustar00rootroot00000000000000 // Coordinate // ---------- // An object representing a tile position, at as specified zoom level. // This is not necessarily a precise tile - `row`, `column`, and // `zoom` can be floating-point numbers, and the `container()` function // can be used to find the actual tile that contains the point. MM.Coordinate = function(row, column, zoom) { this.row = row; this.column = column; this.zoom = zoom; }; MM.Coordinate.prototype = { row: 0, column: 0, zoom: 0, toString: function() { return "(" + this.row.toFixed(3) + ", " + this.column.toFixed(3) + " @" + this.zoom.toFixed(3) + ")"; }, // Quickly generate a string representation of this coordinate to // index it in hashes. toKey: function() { // We've tried to use efficient hash functions here before but we took // them out. Contributions welcome but watch out for collisions when the // row or column are negative and check thoroughly (exhaustively) before // committing. return this.zoom + ',' + this.row + ',' + this.column; }, // Clone this object. copy: function() { return new MM.Coordinate(this.row, this.column, this.zoom); }, // Get the actual, rounded-number tile that contains this point. container: function() { // using floor here (not parseInt, ~~) because we want -0.56 --> -1 return new MM.Coordinate(Math.floor(this.row), Math.floor(this.column), Math.floor(this.zoom)); }, // Recalculate this Coordinate at a different zoom level and return the // new object. zoomTo: function(destination) { var power = Math.pow(2, destination - this.zoom); return new MM.Coordinate(this.row * power, this.column * power, destination); }, // Recalculate this Coordinate at a different relative zoom level and return the // new object. zoomBy: function(distance) { var power = Math.pow(2, distance); return new MM.Coordinate(this.row * power, this.column * power, this.zoom + distance); }, // Move this coordinate up by `dist` coordinates up: function(dist) { if (dist === undefined) dist = 1; return new MM.Coordinate(this.row - dist, this.column, this.zoom); }, // Move this coordinate right by `dist` coordinates right: function(dist) { if (dist === undefined) dist = 1; return new MM.Coordinate(this.row, this.column + dist, this.zoom); }, // Move this coordinate down by `dist` coordinates down: function(dist) { if (dist === undefined) dist = 1; return new MM.Coordinate(this.row + dist, this.column, this.zoom); }, // Move this coordinate left by `dist` coordinates left: function(dist) { if (dist === undefined) dist = 1; return new MM.Coordinate(this.row, this.column - dist, this.zoom); } }; modestmaps-js-3.3.6/src/end.js000066400000000000000000000010041210227260500161740ustar00rootroot00000000000000 if (typeof module !== 'undefined' && module.exports) { module.exports = { Point: MM.Point, Projection: MM.Projection, MercatorProjection: MM.MercatorProjection, LinearProjection: MM.LinearProjection, Transformation: MM.Transformation, Location: MM.Location, MapProvider: MM.MapProvider, Template: MM.Template, Coordinate: MM.Coordinate, deriveTransformation: MM.deriveTransformation }; } })(MM); modestmaps-js-3.3.6/src/extent.js000066400000000000000000000106661210227260500167530ustar00rootroot00000000000000 // Extent // ---------- // An object representing a map's rectangular extent, defined by its north, // south, east and west bounds. MM.Extent = function(north, west, south, east) { if (north instanceof MM.Location && west instanceof MM.Location) { var northwest = north, southeast = west; north = northwest.lat; west = northwest.lon; south = southeast.lat; east = southeast.lon; } if (isNaN(south)) south = north; if (isNaN(east)) east = west; this.north = Math.max(north, south); this.south = Math.min(north, south); this.east = Math.max(east, west); this.west = Math.min(east, west); }; MM.Extent.prototype = { // boundary attributes north: 0, south: 0, east: 0, west: 0, copy: function() { return new MM.Extent(this.north, this.west, this.south, this.east); }, toString: function(precision) { if (isNaN(precision)) precision = 3; return [ this.north.toFixed(precision), this.west.toFixed(precision), this.south.toFixed(precision), this.east.toFixed(precision) ].join(", "); }, // getters for the corner locations northWest: function() { return new MM.Location(this.north, this.west); }, southEast: function() { return new MM.Location(this.south, this.east); }, northEast: function() { return new MM.Location(this.north, this.east); }, southWest: function() { return new MM.Location(this.south, this.west); }, // getter for the center location center: function() { return new MM.Location( this.south + (this.north - this.south) / 2, this.east + (this.west - this.east) / 2 ); }, // extend the bounds to include a location's latitude and longitude encloseLocation: function(loc) { if (loc.lat > this.north) this.north = loc.lat; if (loc.lat < this.south) this.south = loc.lat; if (loc.lon > this.east) this.east = loc.lon; if (loc.lon < this.west) this.west = loc.lon; }, // extend the bounds to include multiple locations encloseLocations: function(locations) { var len = locations.length; for (var i = 0; i < len; i++) { this.encloseLocation(locations[i]); } }, // reset bounds from a list of locations setFromLocations: function(locations) { var len = locations.length, first = locations[0]; this.north = this.south = first.lat; this.east = this.west = first.lon; for (var i = 1; i < len; i++) { this.encloseLocation(locations[i]); } }, // extend the bounds to include another extent encloseExtent: function(extent) { if (extent.north > this.north) this.north = extent.north; if (extent.south < this.south) this.south = extent.south; if (extent.east > this.east) this.east = extent.east; if (extent.west < this.west) this.west = extent.west; }, // determine if a location is within this extent containsLocation: function(loc) { return loc.lat >= this.south && loc.lat <= this.north && loc.lon >= this.west && loc.lon <= this.east; }, // turn an extent into an array of locations containing its northwest // and southeast corners (used in MM.Map.setExtent()) toArray: function() { return [this.northWest(), this.southEast()]; } }; MM.Extent.fromString = function(str) { var parts = str.split(/\s*,\s*/); if (parts.length != 4) { throw "Invalid extent string (expecting 4 comma-separated numbers)"; } return new MM.Extent( parseFloat(parts[0]), parseFloat(parts[1]), parseFloat(parts[2]), parseFloat(parts[3]) ); }; MM.Extent.fromArray = function(locations) { var extent = new MM.Extent(); extent.setFromLocations(locations); return extent; }; modestmaps-js-3.3.6/src/layer.js000066400000000000000000000417051210227260500165560ustar00rootroot00000000000000 // Layer MM.Layer = function(provider, parent, name) { this.parent = parent || document.createElement('div'); this.parent.style.cssText = 'position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; margin: 0; padding: 0; z-index: 0'; this.name = name; this.levels = {}; this.requestManager = new MM.RequestManager(); this.requestManager.addCallback('requestcomplete', this.getTileComplete()); this.requestManager.addCallback('requesterror', this.getTileError()); if (provider) this.setProvider(provider); }; MM.Layer.prototype = { map: null, // TODO: remove parent: null, name: null, enabled: true, tiles: null, levels: null, requestManager: null, provider: null, _tileComplete: null, getTileComplete: function() { if (!this._tileComplete) { var theLayer = this; this._tileComplete = function(manager, tile) { theLayer.tiles[tile.id] = tile; theLayer.positionTile(tile); }; } return this._tileComplete; }, getTileError: function() { if (!this._tileError) { var theLayer = this; this._tileError = function(manager, tile) { tile.element.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'; theLayer.tiles[tile.element.id] = tile.element; theLayer.positionTile(tile.element); }; } return this._tileError; }, draw: function() { if (!this.enabled || !this.map) return; // compares manhattan distance from center of // requested tiles to current map center // NB:- requested tiles are *popped* from queue, so we do a descending sort var theCoord = this.map.coordinate.zoomTo(Math.round(this.map.coordinate.zoom)); function centerDistanceCompare(r1, r2) { if (r1 && r2) { var c1 = r1.coord; var c2 = r2.coord; if (c1.zoom == c2.zoom) { var ds1 = Math.abs(theCoord.row - c1.row - 0.5) + Math.abs(theCoord.column - c1.column - 0.5); var ds2 = Math.abs(theCoord.row - c2.row - 0.5) + Math.abs(theCoord.column - c2.column - 0.5); return ds1 < ds2 ? 1 : ds1 > ds2 ? -1 : 0; } else { return c1.zoom < c2.zoom ? 1 : c1.zoom > c2.zoom ? -1 : 0; } } return r1 ? 1 : r2 ? -1 : 0; } // if we're in between zoom levels, we need to choose the nearest: var baseZoom = Math.round(this.map.coordinate.zoom); // these are the top left and bottom right tile coordinates // we'll be loading everything in between: var startCoord = this.map.pointCoordinate(new MM.Point(0,0)) .zoomTo(baseZoom).container(); var endCoord = this.map.pointCoordinate(this.map.dimensions) .zoomTo(baseZoom).container().right().down(); // tiles with invalid keys will be removed from visible levels // requests for tiles with invalid keys will be canceled // (this object maps from a tile key to a boolean) var validTileKeys = { }; // make sure we have a container for tiles in the current level var levelElement = this.createOrGetLevel(startCoord.zoom); // use this coordinate for generating keys, parents and children: var tileCoord = startCoord.copy(); for (tileCoord.column = startCoord.column; tileCoord.column <= endCoord.column; tileCoord.column++) { for (tileCoord.row = startCoord.row; tileCoord.row <= endCoord.row; tileCoord.row++) { var validKeys = this.inventoryVisibleTile(levelElement, tileCoord); while (validKeys.length) { validTileKeys[validKeys.pop()] = true; } } } // i from i to zoom-5 are levels that would be scaled too big, // i from zoom + 2 to levels. length are levels that would be // scaled too small (and tiles would be too numerous) for (var name in this.levels) { if (this.levels.hasOwnProperty(name)) { var zoom = parseInt(name,10); if (zoom >= startCoord.zoom - 5 && zoom < startCoord.zoom + 2) { continue; } var level = this.levels[name]; level.style.display = 'none'; var visibleTiles = this.tileElementsInLevel(level); while (visibleTiles.length) { this.provider.releaseTile(visibleTiles[0].coord); this.requestManager.clearRequest(visibleTiles[0].coord.toKey()); level.removeChild(visibleTiles[0]); visibleTiles.shift(); } } } // levels we want to see, if they have tiles in validTileKeys var minLevel = startCoord.zoom - 5; var maxLevel = startCoord.zoom + 2; for (var z = minLevel; z < maxLevel; z++) { this.adjustVisibleLevel(this.levels[z], z, validTileKeys); } // cancel requests that aren't visible: this.requestManager.clearExcept(validTileKeys); // get newly requested tiles, sort according to current view: this.requestManager.processQueue(centerDistanceCompare); }, // For a given tile coordinate in a given level element, ensure that it's // correctly represented in the DOM including potentially-overlapping // parent and child tiles for pyramid loading. // // Return a list of valid (i.e. loadable?) tile keys. inventoryVisibleTile: function(layer_element, tile_coord) { var tile_key = tile_coord.toKey(), valid_tile_keys = [tile_key]; // Check that the needed tile already exists someplace - add it to the DOM if it does. if (tile_key in this.tiles) { var tile = this.tiles[tile_key]; // ensure it's in the DOM: if (tile.parentNode != layer_element) { layer_element.appendChild(tile); // if the provider implements reAddTile(), call it if ("reAddTile" in this.provider) { this.provider.reAddTile(tile_key, tile_coord, tile); } } return valid_tile_keys; } // Check that the needed tile has even been requested at all. if (!this.requestManager.hasRequest(tile_key)) { var tileToRequest = this.provider.getTile(tile_coord); if (typeof tileToRequest == 'string') { this.addTileImage(tile_key, tile_coord, tileToRequest); // tile must be truish } else if (tileToRequest) { this.addTileElement(tile_key, tile_coord, tileToRequest); } } // look for a parent tile in our image cache var tileCovered = false; var maxStepsOut = tile_coord.zoom; for (var pz = 1; pz <= maxStepsOut; pz++) { var parent_coord = tile_coord.zoomBy(-pz).container(); var parent_key = parent_coord.toKey(); // only mark it valid if we have it already if (parent_key in this.tiles) { valid_tile_keys.push(parent_key); tileCovered = true; break; } } // if we didn't find a parent, look at the children: if (!tileCovered) { var child_coord = tile_coord.zoomBy(1); // mark everything valid whether or not we have it: valid_tile_keys.push(child_coord.toKey()); child_coord.column += 1; valid_tile_keys.push(child_coord.toKey()); child_coord.row += 1; valid_tile_keys.push(child_coord.toKey()); child_coord.column -= 1; valid_tile_keys.push(child_coord.toKey()); } return valid_tile_keys; }, tileElementsInLevel: function(level) { // this is somewhat future proof, we're looking for DOM elements // not necessarily elements var tiles = []; for (var tile = level.firstChild; tile; tile = tile.nextSibling) { if (tile.nodeType == 1) { tiles.push(tile); } } return tiles; }, /** * For a given level, adjust visibility as a whole and discard individual * tiles based on values in valid_tile_keys from inventoryVisibleTile(). */ adjustVisibleLevel: function(level, zoom, valid_tile_keys) { // no tiles for this level yet if (!level) return; var scale = 1; var theCoord = this.map.coordinate.copy(); if (level.childNodes.length > 0) { level.style.display = 'block'; scale = Math.pow(2, this.map.coordinate.zoom - zoom); theCoord = theCoord.zoomTo(zoom); } else { level.style.display = 'none'; return false; } var tileWidth = this.map.tileSize.x * scale; var tileHeight = this.map.tileSize.y * scale; var center = new MM.Point(this.map.dimensions.x/2, this.map.dimensions.y/2); var tiles = this.tileElementsInLevel(level); while (tiles.length) { var tile = tiles.pop(); if (!valid_tile_keys[tile.id]) { this.provider.releaseTile(tile.coord); this.requestManager.clearRequest(tile.coord.toKey()); level.removeChild(tile); } else { // position tiles MM.moveElement(tile, { x: Math.round(center.x + (tile.coord.column - theCoord.column) * tileWidth), y: Math.round(center.y + (tile.coord.row - theCoord.row) * tileHeight), scale: scale, // TODO: pass only scale or only w/h width: this.map.tileSize.x, height: this.map.tileSize.y }); } } }, createOrGetLevel: function(zoom) { if (zoom in this.levels) { return this.levels[zoom]; } var level = document.createElement('div'); level.id = this.parent.id + '-zoom-' + zoom; level.style.cssText = 'position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; margin: 0; padding: 0;'; level.style.zIndex = zoom; this.parent.appendChild(level); this.levels[zoom] = level; return level; }, addTileImage: function(key, coord, url) { this.requestManager.requestTile(key, coord, url); }, addTileElement: function(key, coordinate, element) { // Expected in draw() element.id = key; element.coord = coordinate.copy(); this.positionTile(element); }, positionTile: function(tile) { // position this tile (avoids a full draw() call): var theCoord = this.map.coordinate.zoomTo(tile.coord.zoom); // Start tile positioning and prevent drag for modern browsers tile.style.cssText = 'position:absolute;-webkit-user-select:none;' + '-webkit-user-drag:none;-moz-user-drag:none;-webkit-transform-origin:0 0;' + '-moz-transform-origin:0 0;-o-transform-origin:0 0;-ms-transform-origin:0 0;' + 'width:' + this.map.tileSize.x + 'px; height: ' + this.map.tileSize.y + 'px;'; // Prevent drag for IE tile.ondragstart = function() { return false; }; var scale = Math.pow(2, this.map.coordinate.zoom - tile.coord.zoom); MM.moveElement(tile, { x: Math.round((this.map.dimensions.x/2) + (tile.coord.column - theCoord.column) * this.map.tileSize.x), y: Math.round((this.map.dimensions.y/2) + (tile.coord.row - theCoord.row) * this.map.tileSize.y), scale: scale, // TODO: pass only scale or only w/h width: this.map.tileSize.x, height: this.map.tileSize.y }); // add tile to its level var theLevel = this.levels[tile.coord.zoom]; theLevel.appendChild(tile); // Support style transition if available. tile.className = 'map-tile-loaded'; // ensure the level is visible if it's still the current level if (Math.round(this.map.coordinate.zoom) == tile.coord.zoom) { theLevel.style.display = 'block'; } // request a lazy redraw of all levels // this will remove tiles that were only visible // to cover this tile while it loaded: this.requestRedraw(); }, _redrawTimer: undefined, requestRedraw: function() { // we'll always draw within 1 second of this request, // sometimes faster if there's already a pending redraw // this is used when a new tile arrives so that we clear // any parent/child tiles that were only being displayed // until the tile loads at the right zoom level if (!this._redrawTimer) { this._redrawTimer = setTimeout(this.getRedraw(), 1000); } }, _redraw: null, getRedraw: function() { // let's only create this closure once... if (!this._redraw) { var theLayer = this; this._redraw = function() { theLayer.draw(); theLayer._redrawTimer = 0; }; } return this._redraw; }, setProvider: function(newProvider) { var firstProvider = (this.provider === null); // if we already have a provider the we'll need to // clear the DOM, cancel requests and redraw if (!firstProvider) { this.requestManager.clear(); for (var name in this.levels) { if (this.levels.hasOwnProperty(name)) { var level = this.levels[name]; while (level.firstChild) { this.provider.releaseTile(level.firstChild.coord); level.removeChild(level.firstChild); } } } } // first provider or not we'll init/reset some values... this.tiles = {}; // for later: check geometry of old provider and set a new coordinate center // if needed (now? or when?) this.provider = newProvider; if (!firstProvider) { this.draw(); } }, // Enable a layer and show its dom element enable: function() { this.enabled = true; this.parent.style.display = ''; this.draw(); return this; }, // Disable a layer, don't display in DOM, clear all requests disable: function() { this.enabled = false; this.requestManager.clear(); this.parent.style.display = 'none'; return this; }, // Remove this layer from the DOM, cancel all of its requests // and unbind any callbacks that are bound to it. destroy: function() { this.requestManager.clear(); this.requestManager.removeCallback('requestcomplete', this.getTileComplete()); this.requestManager.removeCallback('requesterror', this.getTileError()); // TODO: does requestManager need a destroy function too? this.provider = null; // If this layer was ever attached to the DOM, detach it. if (this.parent.parentNode) { this.parent.parentNode.removeChild(this.parent); } this.map = null; } }; modestmaps-js-3.3.6/src/location.js000066400000000000000000000067601210227260500172540ustar00rootroot00000000000000 // Location // -------- MM.Location = function(lat, lon) { this.lat = parseFloat(lat); this.lon = parseFloat(lon); }; MM.Location.prototype = { lat: 0, lon: 0, toString: function() { return "(" + this.lat.toFixed(3) + ", " + this.lon.toFixed(3) + ")"; }, copy: function() { return new MM.Location(this.lat, this.lon); } }; // returns approximate distance between start and end locations // // default unit is meters // // you can specify different units by optionally providing the // earth's radius in the units you desire // // Default is 6,378,000 metres, suggested values are: // // * 3963.1 statute miles // * 3443.9 nautical miles // * 6378 km // // see [Formula and code for calculating distance based on two lat/lon locations](http://jan.ucc.nau.edu/~cvm/latlon_formula.html) MM.Location.distance = function(l1, l2, r) { if (!r) { // default to meters r = 6378000; } var deg2rad = Math.PI / 180.0, a1 = l1.lat * deg2rad, b1 = l1.lon * deg2rad, a2 = l2.lat * deg2rad, b2 = l2.lon * deg2rad, c = Math.cos(a1) * Math.cos(b1) * Math.cos(a2) * Math.cos(b2), d = Math.cos(a1) * Math.sin(b1) * Math.cos(a2) * Math.sin(b2), e = Math.sin(a1) * Math.sin(a2); return Math.acos(c + d + e) * r; }; // Interpolates along a great circle, f between 0 and 1 // // * FIXME: could be heavily optimized (lots of trig calls to cache) // * FIXME: could be inmproved for calculating a full path MM.Location.interpolate = function(l1, l2, f) { if (l1.lat === l2.lat && l1.lon === l2.lon) { return new MM.Location(l1.lat, l1.lon); } var deg2rad = Math.PI / 180.0, lat1 = l1.lat * deg2rad, lon1 = l1.lon * deg2rad, lat2 = l2.lat * deg2rad, lon2 = l2.lon * deg2rad; var d = 2 * Math.asin( Math.sqrt( Math.pow(Math.sin((lat1 - lat2) / 2), 2) + Math.cos(lat1) * Math.cos(lat2) * Math.pow(Math.sin((lon1 - lon2) / 2), 2))); var A = Math.sin((1-f)*d)/Math.sin(d); var B = Math.sin(f*d)/Math.sin(d); var x = A * Math.cos(lat1) * Math.cos(lon1) + B * Math.cos(lat2) * Math.cos(lon2); var y = A * Math.cos(lat1) * Math.sin(lon1) + B * Math.cos(lat2) * Math.sin(lon2); var z = A * Math.sin(lat1) + B * Math.sin(lat2); var latN = Math.atan2(z, Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2))); var lonN = Math.atan2(y,x); return new MM.Location(latN / deg2rad, lonN / deg2rad); }; // Returns bearing from one point to another // // * FIXME: bearing is not constant along significant great circle arcs. MM.Location.bearing = function(l1, l2) { var deg2rad = Math.PI / 180.0, lat1 = l1.lat * deg2rad, lon1 = l1.lon * deg2rad, lat2 = l2.lat * deg2rad, lon2 = l2.lon * deg2rad; var result = Math.atan2( Math.sin(lon1 - lon2) * Math.cos(lat2), Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(lon1 - lon2) ) / -(Math.PI / 180); // map it into 0-360 range return (result < 0) ? result + 360 : result; };modestmaps-js-3.3.6/src/map.js000066400000000000000000000642251210227260500162210ustar00rootroot00000000000000 // Map // Instance of a map intended for drawing to a div. // // * `parent` (required DOM element) // Can also be an ID of a DOM element // * `layerOrLayers` (required MM.Layer or Array of MM.Layers) // each one must implement draw(), destroy(), have a .parent DOM element and a .map property // (an array of URL templates or MM.MapProviders is also acceptable) // * `dimensions` (optional Point) // Size of map to create // * `eventHandlers` (optional Array) // If empty or null MouseHandler will be used // Otherwise, each handler will be called with init(map) MM.Map = function(parent, layerOrLayers, dimensions, eventHandlers) { if (typeof parent == 'string') { parent = document.getElementById(parent); if (!parent) { throw 'The ID provided to modest maps could not be found.'; } } this.parent = parent; // we're no longer adding width and height to parent.style but we still // need to enforce padding, overflow and position otherwise everything screws up // TODO: maybe console.warn if the current values are bad? this.parent.style.padding = '0'; this.parent.style.overflow = 'hidden'; var position = MM.getStyle(this.parent, 'position'); if (position != 'relative' && position != 'absolute') { this.parent.style.position = 'relative'; } this.layers = []; if (!layerOrLayers) { layerOrLayers = []; } if (!(layerOrLayers instanceof Array)) { layerOrLayers = [ layerOrLayers ]; } for (var i = 0; i < layerOrLayers.length; i++) { this.addLayer(layerOrLayers[i]); } // default to Google-y Mercator style maps this.projection = new MM.MercatorProjection(0, MM.deriveTransformation(-Math.PI, Math.PI, 0, 0, Math.PI, Math.PI, 1, 0, -Math.PI, -Math.PI, 0, 1)); this.tileSize = new MM.Point(256, 256); // default 0-18 zoom level // with infinite horizontal pan and clamped vertical pan this.coordLimits = [ new MM.Coordinate(0,-Infinity,0), // top left outer new MM.Coordinate(1,Infinity,0).zoomTo(18) // bottom right inner ]; // eyes towards null island this.coordinate = new MM.Coordinate(0.5, 0.5, 0); // if you don't specify dimensions we assume you want to fill the parent // unless the parent has no w/h, in which case we'll still use a default if (!dimensions) { dimensions = new MM.Point(this.parent.offsetWidth, this.parent.offsetHeight); this.autoSize = true; // use destroy to get rid of this handler from the DOM MM.addEvent(window, 'resize', this.windowResize()); } else { this.autoSize = false; // don't call setSize here because it calls draw() this.parent.style.width = Math.round(dimensions.x) + 'px'; this.parent.style.height = Math.round(dimensions.y) + 'px'; } this.dimensions = dimensions; this.callbackManager = new MM.CallbackManager(this, [ 'zoomed', 'panned', 'centered', 'extentset', 'resized', 'drawn' ]); // set up handlers last so that all required attributes/functions are in place if needed if (eventHandlers === undefined) { this.eventHandlers = [ MM.MouseHandler().init(this), MM.TouchHandler().init(this) ]; } else { this.eventHandlers = eventHandlers; if (eventHandlers instanceof Array) { for (var j = 0; j < eventHandlers.length; j++) { eventHandlers[j].init(this); } } } }; MM.Map.prototype = { parent: null, // DOM Element dimensions: null, // MM.Point with x/y size of parent element projection: null, // MM.Projection of first known layer coordinate: null, // Center of map MM.Coordinate with row/column/zoom tileSize: null, // MM.Point with x/y size of tiles coordLimits: null, // Array of [ topLeftOuter, bottomLeftInner ] MM.Coordinates layers: null, // Array of MM.Layer (interface = .draw(), .destroy(), .parent and .map) callbackManager: null, // MM.CallbackManager, handles map events eventHandlers: null, // Array of interaction handlers, just a MM.MouseHandler by default autoSize: null, // Boolean, true if we have a window resize listener toString: function() { return 'Map(#' + this.parent.id + ')'; }, // callbacks... addCallback: function(event, callback) { this.callbackManager.addCallback(event, callback); return this; }, removeCallback: function(event, callback) { this.callbackManager.removeCallback(event, callback); return this; }, dispatchCallback: function(event, message) { this.callbackManager.dispatchCallback(event, message); return this; }, windowResize: function() { if (!this._windowResize) { var theMap = this; this._windowResize = function(event) { // don't call setSize here because it sets parent.style.width/height // and setting the height breaks percentages and default styles theMap.dimensions = new MM.Point(theMap.parent.offsetWidth, theMap.parent.offsetHeight); theMap.draw(); theMap.dispatchCallback('resized', [theMap.dimensions]); }; } return this._windowResize; }, // A convenience function to restrict interactive zoom ranges. // (you should also adjust map provider to restrict which tiles get loaded, // or modify map.coordLimits and provider.tileLimits for finer control) setZoomRange: function(minZoom, maxZoom) { this.coordLimits[0] = this.coordLimits[0].zoomTo(minZoom); this.coordLimits[1] = this.coordLimits[1].zoomTo(maxZoom); return this; }, // zooming zoomBy: function(zoomOffset) { this.coordinate = this.enforceLimits(this.coordinate.zoomBy(zoomOffset)); MM.getFrame(this.getRedraw()); this.dispatchCallback('zoomed', zoomOffset); return this; }, zoomIn: function() { return this.zoomBy(1); }, zoomOut: function() { return this.zoomBy(-1); }, setZoom: function(z) { return this.zoomBy(z - this.coordinate.zoom); }, zoomByAbout: function(zoomOffset, point) { var location = this.pointLocation(point); this.coordinate = this.enforceLimits(this.coordinate.zoomBy(zoomOffset)); var newPoint = this.locationPoint(location); this.dispatchCallback('zoomed', zoomOffset); return this.panBy(point.x - newPoint.x, point.y - newPoint.y); }, // panning panBy: function(dx, dy) { this.coordinate.column -= dx / this.tileSize.x; this.coordinate.row -= dy / this.tileSize.y; this.coordinate = this.enforceLimits(this.coordinate); // Defer until the browser is ready to draw. MM.getFrame(this.getRedraw()); this.dispatchCallback('panned', [dx, dy]); return this; }, panLeft: function() { return this.panBy(100, 0); }, panRight: function() { return this.panBy(-100, 0); }, panDown: function() { return this.panBy(0, -100); }, panUp: function() { return this.panBy(0, 100); }, // positioning setCenter: function(location) { return this.setCenterZoom(location, this.coordinate.zoom); }, setCenterZoom: function(location, zoom) { this.coordinate = this.projection.locationCoordinate(location).zoomTo(parseFloat(zoom) || 0); this.coordinate = this.enforceLimits(this.coordinate); MM.getFrame(this.getRedraw()); this.dispatchCallback('centered', [location, zoom]); return this; }, extentCoordinate: function(locations, precise) { // coerce locations to an array if it's a Extent instance if (locations instanceof MM.Extent) { locations = locations.toArray(); } var TL, BR; for (var i = 0; i < locations.length; i++) { var coordinate = this.projection.locationCoordinate(locations[i]); if (TL) { TL.row = Math.min(TL.row, coordinate.row); TL.column = Math.min(TL.column, coordinate.column); TL.zoom = Math.min(TL.zoom, coordinate.zoom); BR.row = Math.max(BR.row, coordinate.row); BR.column = Math.max(BR.column, coordinate.column); BR.zoom = Math.max(BR.zoom, coordinate.zoom); } else { TL = coordinate.copy(); BR = coordinate.copy(); } } var width = this.dimensions.x + 1; var height = this.dimensions.y + 1; // multiplication factor between horizontal span and map width var hFactor = (BR.column - TL.column) / (width / this.tileSize.x); // multiplication factor expressed as base-2 logarithm, for zoom difference var hZoomDiff = Math.log(hFactor) / Math.log(2); // possible horizontal zoom to fit geographical extent in map width var hPossibleZoom = TL.zoom - (precise ? hZoomDiff : Math.ceil(hZoomDiff)); // multiplication factor between vertical span and map height var vFactor = (BR.row - TL.row) / (height / this.tileSize.y); // multiplication factor expressed as base-2 logarithm, for zoom difference var vZoomDiff = Math.log(vFactor) / Math.log(2); // possible vertical zoom to fit geographical extent in map height var vPossibleZoom = TL.zoom - (precise ? vZoomDiff : Math.ceil(vZoomDiff)); // initial zoom to fit extent vertically and horizontally var initZoom = Math.min(hPossibleZoom, vPossibleZoom); // additionally, make sure it's not outside the boundaries set by map limits initZoom = Math.min(initZoom, this.coordLimits[1].zoom); initZoom = Math.max(initZoom, this.coordLimits[0].zoom); // coordinate of extent center var centerRow = (TL.row + BR.row) / 2; var centerColumn = (TL.column + BR.column) / 2; var centerZoom = TL.zoom; return new MM.Coordinate(centerRow, centerColumn, centerZoom).zoomTo(initZoom); }, setExtent: function(locations, precise) { this.coordinate = this.extentCoordinate(locations, precise); this.coordinate = this.enforceLimits(this.coordinate); MM.getFrame(this.getRedraw()); this.dispatchCallback('extentset', locations); return this; }, // Resize the map's container `
`, redrawing the map and triggering // `resized` to make sure that the map's presentation is still correct. setSize: function(dimensions) { // Ensure that, whether a raw object or a Point object is passed, // this.dimensions will be a Point. this.dimensions = new MM.Point(dimensions.x, dimensions.y); this.parent.style.width = Math.round(this.dimensions.x) + 'px'; this.parent.style.height = Math.round(this.dimensions.y) + 'px'; if (this.autoSize) { MM.removeEvent(window, 'resize', this.windowResize()); this.autoSize = false; } this.draw(); // draw calls enforceLimits // (if you switch to getFrame, call enforceLimits first) this.dispatchCallback('resized', this.dimensions); return this; }, // projecting points on and off screen coordinatePoint: function(coord) { // Return an x, y point on the map image for a given coordinate. if (coord.zoom != this.coordinate.zoom) { coord = coord.zoomTo(this.coordinate.zoom); } // distance from the center of the map var point = new MM.Point(this.dimensions.x / 2, this.dimensions.y / 2); point.x += this.tileSize.x * (coord.column - this.coordinate.column); point.y += this.tileSize.y * (coord.row - this.coordinate.row); return point; }, // Get a `MM.Coordinate` from an `MM.Point` - returns a new tile-like object // from a screen point. pointCoordinate: function(point) { // new point coordinate reflecting distance from map center, in tile widths var coord = this.coordinate.copy(); coord.column += (point.x - this.dimensions.x / 2) / this.tileSize.x; coord.row += (point.y - this.dimensions.y / 2) / this.tileSize.y; return coord; }, // Return an MM.Coordinate (row,col,zoom) for an MM.Location (lat,lon). locationCoordinate: function(location) { return this.projection.locationCoordinate(location); }, // Return an MM.Location (lat,lon) for an MM.Coordinate (row,col,zoom). coordinateLocation: function(coordinate) { return this.projection.coordinateLocation(coordinate); }, // Return an x, y point on the map image for a given geographical location. locationPoint: function(location) { return this.coordinatePoint(this.locationCoordinate(location)); }, // Return a geographical location on the map image for a given x, y point. pointLocation: function(point) { return this.coordinateLocation(this.pointCoordinate(point)); }, // inspecting getExtent: function() { return new MM.Extent( this.pointLocation(new MM.Point(0, 0)), this.pointLocation(this.dimensions) ); }, extent: function(locations, precise) { if (locations) { return this.setExtent(locations, precise); } else { return this.getExtent(); } }, // Get the current centerpoint of the map, returning a `Location` getCenter: function() { return this.projection.coordinateLocation(this.coordinate); }, center: function(location) { if (location) { return this.setCenter(location); } else { return this.getCenter(); } }, // Get the current zoom level of the map, returning a number getZoom: function() { return this.coordinate.zoom; }, zoom: function(zoom) { if (zoom !== undefined) { return this.setZoom(zoom); } else { return this.getZoom(); } }, // return a copy of the layers array getLayers: function() { return this.layers.slice(); }, // return the first layer with given name getLayer: function(name) { for (var i = 0; i < this.layers.length; i++) { if (name == this.layers[i].name) return this.layers[i]; } }, // return the layer at the given index getLayerAt: function(index) { return this.layers[index]; }, // put the given layer on top of all the others // Since this is called for the first layer, which is by definition // added before the map has a valid `coordinate`, we request // a redraw only if the map has a center coordinate. addLayer: function(layer) { this.layers.push(layer); this.parent.appendChild(layer.parent); layer.map = this; // TODO: remove map property from MM.Layer? if (this.coordinate) { MM.getFrame(this.getRedraw()); } return this; }, // find the given layer and remove it removeLayer: function(layer) { for (var i = 0; i < this.layers.length; i++) { if (layer == this.layers[i] || layer == this.layers[i].name) { this.removeLayerAt(i); break; } } return this; }, // replace the current layer at the given index with the given layer setLayerAt: function(index, layer) { if (index < 0 || index >= this.layers.length) { throw new Error('invalid index in setLayerAt(): ' + index); } if (this.layers[index] != layer) { // clear existing layer at this index if (index < this.layers.length) { var other = this.layers[index]; this.parent.insertBefore(layer.parent, other.parent); other.destroy(); } else { // Or if this will be the last layer, it can be simply appended this.parent.appendChild(layer.parent); } this.layers[index] = layer; layer.map = this; // TODO: remove map property from MM.Layer MM.getFrame(this.getRedraw()); } return this; }, // put the given layer at the given index, moving others if necessary insertLayerAt: function(index, layer) { if (index < 0 || index > this.layers.length) { throw new Error('invalid index in insertLayerAt(): ' + index); } if (index == this.layers.length) { // it just gets tacked on to the end this.layers.push(layer); this.parent.appendChild(layer.parent); } else { // it needs to get slipped in amongst the others var other = this.layers[index]; this.parent.insertBefore(layer.parent, other.parent); this.layers.splice(index, 0, layer); } layer.map = this; // TODO: remove map property from MM.Layer MM.getFrame(this.getRedraw()); return this; }, // remove the layer at the given index, call .destroy() on the layer removeLayerAt: function(index) { if (index < 0 || index >= this.layers.length) { throw new Error('invalid index in removeLayer(): ' + index); } // gone baby gone. var old = this.layers[index]; this.layers.splice(index, 1); old.destroy(); return this; }, // switch the stacking order of two layers, by index swapLayersAt: function(i, j) { if (i < 0 || i >= this.layers.length || j < 0 || j >= this.layers.length) { throw new Error('invalid index in swapLayersAt(): ' + index); } var layer1 = this.layers[i], layer2 = this.layers[j], dummy = document.createElement('div'); // kick layer2 out, replace it with the dummy. this.parent.replaceChild(dummy, layer2.parent); // put layer2 back in and kick layer1 out this.parent.replaceChild(layer2.parent, layer1.parent); // put layer1 back in and ditch the dummy this.parent.replaceChild(layer1.parent, dummy); // now do it to the layers array this.layers[i] = layer2; this.layers[j] = layer1; return this; }, // Enable and disable layers. // Disabled layers are not displayed, are not drawn, and do not request // tiles. They do maintain their layer index on the map. enableLayer: function(name) { var l = this.getLayer(name); if (l) l.enable(); return this; }, enableLayerAt: function(index) { var l = this.getLayerAt(index); if (l) l.enable(); return this; }, disableLayer: function(name) { var l = this.getLayer(name); if (l) l.disable(); return this; }, disableLayerAt: function(index) { var l = this.getLayerAt(index); if (l) l.disable(); return this; }, // limits enforceZoomLimits: function(coord) { var limits = this.coordLimits; if (limits) { // clamp zoom level: var minZoom = limits[0].zoom; var maxZoom = limits[1].zoom; if (coord.zoom < minZoom) { coord = coord.zoomTo(minZoom); } else if (coord.zoom > maxZoom) { coord = coord.zoomTo(maxZoom); } } return coord; }, enforcePanLimits: function(coord) { if (this.coordLimits) { coord = coord.copy(); // clamp pan: var topLeftLimit = this.coordLimits[0].zoomTo(coord.zoom); var bottomRightLimit = this.coordLimits[1].zoomTo(coord.zoom); var currentTopLeft = this.pointCoordinate(new MM.Point(0, 0)) .zoomTo(coord.zoom); var currentBottomRight = this.pointCoordinate(this.dimensions) .zoomTo(coord.zoom); // this handles infinite limits: // (Infinity - Infinity) is Nan // NaN is never less than anything if (bottomRightLimit.row - topLeftLimit.row < currentBottomRight.row - currentTopLeft.row) { // if the limit is smaller than the current view center it coord.row = (bottomRightLimit.row + topLeftLimit.row) / 2; } else { if (currentTopLeft.row < topLeftLimit.row) { coord.row += topLeftLimit.row - currentTopLeft.row; } else if (currentBottomRight.row > bottomRightLimit.row) { coord.row -= currentBottomRight.row - bottomRightLimit.row; } } if (bottomRightLimit.column - topLeftLimit.column < currentBottomRight.column - currentTopLeft.column) { // if the limit is smaller than the current view, center it coord.column = (bottomRightLimit.column + topLeftLimit.column) / 2; } else { if (currentTopLeft.column < topLeftLimit.column) { coord.column += topLeftLimit.column - currentTopLeft.column; } else if (currentBottomRight.column > bottomRightLimit.column) { coord.column -= currentBottomRight.column - bottomRightLimit.column; } } } return coord; }, // Prevent accidentally navigating outside the `coordLimits` of the map. enforceLimits: function(coord) { return this.enforcePanLimits(this.enforceZoomLimits(coord)); }, // rendering // Redraw the tiles on the map, reusing existing tiles. draw: function() { // make sure we're not too far in or out: this.coordinate = this.enforceLimits(this.coordinate); // if we don't have dimensions, check the parent size if (this.dimensions.x <= 0 || this.dimensions.y <= 0) { if (this.autoSize) { // maybe the parent size has changed? var w = this.parent.offsetWidth, h = this.parent.offsetHeight; this.dimensions = new MM.Point(w,h); if (w <= 0 || h <= 0) { return; } } else { // the issue can only be corrected with setSize return; } } // draw layers one by one for(var i = 0; i < this.layers.length; i++) { this.layers[i].draw(); } this.dispatchCallback('drawn'); }, _redrawTimer: undefined, requestRedraw: function() { // we'll always draw within 1 second of this request, // sometimes faster if there's already a pending redraw // this is used when a new tile arrives so that we clear // any parent/child tiles that were only being displayed // until the tile loads at the right zoom level if (!this._redrawTimer) { this._redrawTimer = setTimeout(this.getRedraw(), 1000); } }, _redraw: null, getRedraw: function() { // let's only create this closure once... if (!this._redraw) { var theMap = this; this._redraw = function() { theMap.draw(); theMap._redrawTimer = 0; }; } return this._redraw; }, // Attempts to destroy all attachment a map has to a page // and clear its memory usage. destroy: function() { for (var j = 0; j < this.layers.length; j++) { this.layers[j].destroy(); } this.layers = []; this.projection = null; for (var i = 0; i < this.eventHandlers.length; i++) { this.eventHandlers[i].remove(); } if (this.autoSize) { MM.removeEvent(window, 'resize', this.windowResize()); } } }; modestmaps-js-3.3.6/src/mouse.js000066400000000000000000000125271210227260500165720ustar00rootroot00000000000000 // Event Handlers // -------------- // A utility function for finding the offset of the // mouse from the top-left of the page MM.getMousePoint = function(e, map) { // start with just the mouse (x, y) var point = new MM.Point(e.clientX, e.clientY); // correct for scrolled document point.x += document.body.scrollLeft + document.documentElement.scrollLeft; point.y += document.body.scrollTop + document.documentElement.scrollTop; // correct for nested offsets in DOM for (var node = map.parent; node; node = node.offsetParent) { point.x -= node.offsetLeft; point.y -= node.offsetTop; } return point; }; MM.MouseWheelHandler = function() { var handler = {}, map, _zoomDiv, prevTime, precise = false; function mouseWheel(e) { var delta = 0; prevTime = prevTime || new Date().getTime(); try { _zoomDiv.scrollTop = 1000; _zoomDiv.dispatchEvent(e); delta = 1000 - _zoomDiv.scrollTop; } catch (error) { delta = e.wheelDelta || (-e.detail * 5); } // limit mousewheeling to once every 200ms var timeSince = new Date().getTime() - prevTime; var point = MM.getMousePoint(e, map); if (Math.abs(delta) > 0 && (timeSince > 200) && !precise) { map.zoomByAbout(delta > 0 ? 1 : -1, point); prevTime = new Date().getTime(); } else if (precise) { map.zoomByAbout(delta * 0.001, point); } // Cancel the event so that the page doesn't scroll return MM.cancelEvent(e); } handler.init = function(x) { map = x; _zoomDiv = document.body.appendChild(document.createElement('div')); _zoomDiv.style.cssText = 'visibility:hidden;top:0;height:0;width:0;overflow-y:scroll'; var innerDiv = _zoomDiv.appendChild(document.createElement('div')); innerDiv.style.height = '2000px'; MM.addEvent(map.parent, 'mousewheel', mouseWheel); return handler; }; handler.precise = function(x) { if (!arguments.length) return precise; precise = x; return handler; }; handler.remove = function() { MM.removeEvent(map.parent, 'mousewheel', mouseWheel); _zoomDiv.parentNode.removeChild(_zoomDiv); }; return handler; }; MM.DoubleClickHandler = function() { var handler = {}, map; function doubleClick(e) { // Ensure that this handler is attached once. // Get the point on the map that was double-clicked var point = MM.getMousePoint(e, map); // use shift-double-click to zoom out map.zoomByAbout(e.shiftKey ? -1 : 1, point); return MM.cancelEvent(e); } handler.init = function(x) { map = x; MM.addEvent(map.parent, 'dblclick', doubleClick); return handler; }; handler.remove = function() { MM.removeEvent(map.parent, 'dblclick', doubleClick); }; return handler; }; // Handle the use of mouse dragging to pan the map. MM.DragHandler = function() { var handler = {}, prevMouse, map; function mouseDown(e) { if (e.shiftKey || e.button == 2) return; MM.addEvent(document, 'mouseup', mouseUp); MM.addEvent(document, 'mousemove', mouseMove); prevMouse = new MM.Point(e.clientX, e.clientY); map.parent.style.cursor = 'move'; return MM.cancelEvent(e); } function mouseUp(e) { MM.removeEvent(document, 'mouseup', mouseUp); MM.removeEvent(document, 'mousemove', mouseMove); prevMouse = null; map.parent.style.cursor = ''; return MM.cancelEvent(e); } function mouseMove(e) { if (prevMouse) { map.panBy( e.clientX - prevMouse.x, e.clientY - prevMouse.y); prevMouse.x = e.clientX; prevMouse.y = e.clientY; prevMouse.t = +new Date(); } return MM.cancelEvent(e); } handler.init = function(x) { map = x; MM.addEvent(map.parent, 'mousedown', mouseDown); return handler; }; handler.remove = function() { MM.removeEvent(map.parent, 'mousedown', mouseDown); }; return handler; }; MM.MouseHandler = function() { var handler = {}, map, handlers; handler.init = function(x) { map = x; handlers = [ MM.DragHandler().init(map), MM.DoubleClickHandler().init(map), MM.MouseWheelHandler().init(map) ]; return handler; }; handler.remove = function() { for (var i = 0; i < handlers.length; i++) { handlers[i].remove(); } return handler; }; return handler; }; modestmaps-js-3.3.6/src/point.js000066400000000000000000000014571210227260500165730ustar00rootroot00000000000000 // Point MM.Point = function(x, y) { this.x = parseFloat(x); this.y = parseFloat(y); }; MM.Point.prototype = { x: 0, y: 0, toString: function() { return "(" + this.x.toFixed(3) + ", " + this.y.toFixed(3) + ")"; }, copy: function() { return new MM.Point(this.x, this.y); } }; // Get the euclidean distance between two points MM.Point.distance = function(p1, p2) { return Math.sqrt( Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2)); }; // Get a point between two other points, biased by `t`. MM.Point.interpolate = function(p1, p2, t) { return new MM.Point( p1.x + (p2.x - p1.x) * t, p1.y + (p2.y - p1.y) * t); }; modestmaps-js-3.3.6/src/projection.js000066400000000000000000000056531210227260500176200ustar00rootroot00000000000000 // Projection // ---------- // An abstract class / interface for projections MM.Projection = function(zoom, transformation) { if (!transformation) { transformation = new MM.Transformation(1, 0, 0, 0, 1, 0); } this.zoom = zoom; this.transformation = transformation; }; MM.Projection.prototype = { zoom: 0, transformation: null, rawProject: function(point) { throw "Abstract method not implemented by subclass."; }, rawUnproject: function(point) { throw "Abstract method not implemented by subclass."; }, project: function(point) { point = this.rawProject(point); if(this.transformation) { point = this.transformation.transform(point); } return point; }, unproject: function(point) { if(this.transformation) { point = this.transformation.untransform(point); } point = this.rawUnproject(point); return point; }, locationCoordinate: function(location) { var point = new MM.Point(Math.PI * location.lon / 180.0, Math.PI * location.lat / 180.0); point = this.project(point); return new MM.Coordinate(point.y, point.x, this.zoom); }, coordinateLocation: function(coordinate) { coordinate = coordinate.zoomTo(this.zoom); var point = new MM.Point(coordinate.column, coordinate.row); point = this.unproject(point); return new MM.Location(180.0 * point.y / Math.PI, 180.0 * point.x / Math.PI); } }; // A projection for equilateral maps, based on longitude and latitude MM.LinearProjection = function(zoom, transformation) { MM.Projection.call(this, zoom, transformation); }; // The Linear projection doesn't reproject points MM.LinearProjection.prototype = { rawProject: function(point) { return new MM.Point(point.x, point.y); }, rawUnproject: function(point) { return new MM.Point(point.x, point.y); } }; MM.extend(MM.LinearProjection, MM.Projection); MM.MercatorProjection = function(zoom, transformation) { // super! MM.Projection.call(this, zoom, transformation); }; // Project lon/lat points into meters required for Mercator MM.MercatorProjection.prototype = { rawProject: function(point) { return new MM.Point(point.x, Math.log(Math.tan(0.25 * Math.PI + 0.5 * point.y))); }, rawUnproject: function(point) { return new MM.Point(point.x, 2 * Math.atan(Math.pow(Math.E, point.y)) - 0.5 * Math.PI); } }; MM.extend(MM.MercatorProjection, MM.Projection); modestmaps-js-3.3.6/src/provider.js000066400000000000000000000122451210227260500172710ustar00rootroot00000000000000 // Providers // --------- // Providers provide tile URLs and possibly elements for layers. // // MapProvider -> // Template // MM.MapProvider = function(getTile) { if (getTile) { this.getTile = getTile; } }; MM.MapProvider.prototype = { // these are limits for available *tiles* // panning limits will be different (since you can wrap around columns) // but if you put Infinity in here it will screw up sourceCoordinate tileLimits: [ new MM.Coordinate(0,0,0), // top left outer new MM.Coordinate(1,1,0).zoomTo(18) // bottom right inner ], getTileUrl: function(coordinate) { throw "Abstract method not implemented by subclass."; }, getTile: function(coordinate) { throw "Abstract method not implemented by subclass."; }, // releaseTile is not required releaseTile: function(element) { }, // use this to tell MapProvider that tiles only exist between certain zoom levels. // should be set separately on Map to restrict interactive zoom/pan ranges setZoomRange: function(minZoom, maxZoom) { this.tileLimits[0] = this.tileLimits[0].zoomTo(minZoom); this.tileLimits[1] = this.tileLimits[1].zoomTo(maxZoom); }, // wrap column around the world if necessary // return null if wrapped coordinate is outside of the tile limits sourceCoordinate: function(coord) { var TL = this.tileLimits[0].zoomTo(coord.zoom).container(), BR = this.tileLimits[1].zoomTo(coord.zoom), columnSize = Math.pow(2, coord.zoom), wrappedColumn; BR = new MM.Coordinate(Math.ceil(BR.row), Math.ceil(BR.column), Math.floor(BR.zoom)); if (coord.column < 0) { wrappedColumn = ((coord.column % columnSize) + columnSize) % columnSize; } else { wrappedColumn = coord.column % columnSize; } if (coord.row < TL.row || coord.row >= BR.row) { return null; } else if (wrappedColumn < TL.column || wrappedColumn >= BR.column) { return null; } else { return new MM.Coordinate(coord.row, wrappedColumn, coord.zoom); } } }; /** * FIXME: need a better explanation here! This is a pretty crucial part of * understanding how to use ModestMaps. * * TemplatedMapProvider is a tile provider that generates tile URLs from a * template string by replacing the following bits for each tile * coordinate: * * {Z}: the tile's zoom level (from 1 to ~20) * {X}: the tile's X, or column (from 0 to a very large number at higher * zooms) * {Y}: the tile's Y, or row (from 0 to a very large number at higher * zooms) * * E.g.: * * var osm = new MM.TemplatedMapProvider("http://tile.openstreetmap.org/{Z}/{X}/{Y}.png"); * * Or: * * var placeholder = new MM.TemplatedMapProvider("http://placehold.it/256/f0f/fff.png&text={Z}/{X}/{Y}"); * */ MM.Template = function(template, subdomains) { var isQuadKey = template.match(/{(Q|quadkey)}/); // replace Microsoft style substitution strings if (isQuadKey) template = template .replace('{subdomains}', '{S}') .replace('{zoom}', '{Z}') .replace('{quadkey}', '{Q}'); var hasSubdomains = (subdomains && subdomains.length && template.indexOf("{S}") >= 0); function quadKey (row, column, zoom) { var key = ''; for (var i = 1; i <= zoom; i++) { key += (((row >> zoom - i) & 1) << 1) | ((column >> zoom - i) & 1); } return key || '0'; } var getTileUrl = function(coordinate) { var coord = this.sourceCoordinate(coordinate); if (!coord) { return null; } var base = template; if (hasSubdomains) { var index = parseInt(coord.zoom + coord.row + coord.column, 10) % subdomains.length; base = base.replace('{S}', subdomains[index]); } if (isQuadKey) { return base .replace('{Z}', coord.zoom.toFixed(0)) .replace('{Q}', quadKey(coord.row, coord.column, coord.zoom)); } else { return base .replace('{Z}', coord.zoom.toFixed(0)) .replace('{X}', coord.column.toFixed(0)) .replace('{Y}', coord.row.toFixed(0)); } }; MM.MapProvider.call(this, getTileUrl); }; MM.Template.prototype = { // quadKey generator getTile: function(coord) { return this.getTileUrl(coord); } }; MM.extend(MM.Template, MM.MapProvider); MM.TemplatedLayer = function(template, subdomains, name) { return new MM.Layer(new MM.Template(template, subdomains), null, name); }; modestmaps-js-3.3.6/src/requests.js000066400000000000000000000242751210227260500173200ustar00rootroot00000000000000 // RequestManager // -------------- // an image loading queue MM.RequestManager = function() { // The loading bay is a document fragment to optimize appending, since // the elements within are invisible. See // [this blog post](http://ejohn.org/blog/dom-documentfragments/). this.loadingBay = document.createDocumentFragment(); this.requestsById = {}; this.openRequestCount = 0; this.maxOpenRequests = 4; this.requestQueue = []; this.callbackManager = new MM.CallbackManager(this, [ 'requestcomplete', 'requesterror']); }; MM.RequestManager.prototype = { // DOM element, hidden, for making sure images dispatch complete events loadingBay: null, // all known requests, by ID requestsById: null, // current pending requests requestQueue: null, // current open requests (children of loadingBay) openRequestCount: null, // the number of open requests permitted at one time, clamped down // because of domain-connection limits. maxOpenRequests: null, // for dispatching 'requestcomplete' callbackManager: null, addCallback: function(event, callback) { this.callbackManager.addCallback(event,callback); }, removeCallback: function(event, callback) { this.callbackManager.removeCallback(event,callback); }, dispatchCallback: function(event, message) { this.callbackManager.dispatchCallback(event,message); }, // Clear everything in the queue by excluding nothing clear: function() { this.clearExcept({}); }, clearRequest: function(id) { if(id in this.requestsById) { delete this.requestsById[id]; } for(var i = 0; i < this.requestQueue.length; i++) { var request = this.requestQueue[i]; if(request && request.id == id) { this.requestQueue[i] = null; } } }, // Clear everything in the queue except for certain keys, specified // by an object of the form // // { key: throwawayvalue } clearExcept: function(validIds) { // clear things from the queue first... for (var i = 0; i < this.requestQueue.length; i++) { var request = this.requestQueue[i]; if (request && !(request.id in validIds)) { this.requestQueue[i] = null; } } // then check the loadingBay... var openRequests = this.loadingBay.childNodes; for (var j = openRequests.length-1; j >= 0; j--) { var img = openRequests[j]; if (!(img.id in validIds)) { this.loadingBay.removeChild(img); this.openRequestCount--; /* console.log(this.openRequestCount + " open requests"); */ img.src = img.coord = img.onload = img.onerror = null; } } // hasOwnProperty protects against prototype additions // > "The standard describes an augmentable Object.prototype. // Ignore standards at your own peril." // -- http://www.yuiblog.com/blog/2006/09/26/for-in-intrigue/ for (var id in this.requestsById) { if (!(id in validIds)) { if (this.requestsById.hasOwnProperty(id)) { var requestToRemove = this.requestsById[id]; // whether we've done the request or not... delete this.requestsById[id]; if (requestToRemove !== null) { requestToRemove = requestToRemove.id = requestToRemove.coord = requestToRemove.url = null; } } } } }, // Given a tile id, check whether the RequestManager is currently // requesting it and waiting for the result. hasRequest: function(id) { return (id in this.requestsById); }, // * TODO: remove dependency on coord (it's for sorting, maybe call it data?) // * TODO: rename to requestImage once it's not tile specific requestTile: function(id, coord, url) { if (!(id in this.requestsById)) { var request = { id: id, coord: coord.copy(), url: url }; // if there's no url just make sure we don't request this image again this.requestsById[id] = request; if (url) { this.requestQueue.push(request); /* console.log(this.requestQueue.length + ' pending requests'); */ } } }, getProcessQueue: function() { // let's only create this closure once... if (!this._processQueue) { var theManager = this; this._processQueue = function() { theManager.processQueue(); }; } return this._processQueue; }, // Select images from the `requestQueue` and create image elements for // them, attaching their load events to the function returned by // `this.getLoadComplete()` so that they can be added to the map. processQueue: function(sortFunc) { // When the request queue fills up beyond 8, start sorting the // requests so that spiral-loading or another pattern can be used. if (sortFunc && this.requestQueue.length > 8) { this.requestQueue.sort(sortFunc); } while (this.openRequestCount < this.maxOpenRequests && this.requestQueue.length > 0) { var request = this.requestQueue.pop(); if (request) { this.openRequestCount++; /* console.log(this.openRequestCount + ' open requests'); */ // JSLitmus benchmark shows createElement is a little faster than // new Image() in Firefox and roughly the same in Safari: // http://tinyurl.com/y9wz2jj http://tinyurl.com/yes6rrt var img = document.createElement('img'); // FIXME: id is technically not unique in document if there // are two Maps but toKey is supposed to be fast so we're trying // to avoid a prefix ... hence we can't use any calls to // `document.getElementById()` to retrieve images img.id = request.id; img.style.position = 'absolute'; // * FIXME: store this elsewhere to avoid scary memory leaks? // * FIXME: call this 'data' not 'coord' so that RequestManager is less Tile-centric? img.coord = request.coord; // add it to the DOM in a hidden layer, this is a bit of a hack, but it's // so that the event we get in image.onload has srcElement assigned in IE6 this.loadingBay.appendChild(img); // set these before img.src to avoid missing an img that's already cached img.onload = img.onerror = this.getLoadComplete(); img.src = request.url; // keep things tidy request = request.id = request.coord = request.url = null; } } }, _loadComplete: null, // Get the singleton `_loadComplete` function that is called on image // load events, either removing them from the queue and dispatching an // event to add them to the map, or deleting them if the image failed // to load. getLoadComplete: function() { // let's only create this closure once... if (!this._loadComplete) { var theManager = this; this._loadComplete = function(e) { // this is needed because we don't use MM.addEvent for images e = e || window.event; // srcElement for IE, target for FF, Safari etc. var img = e.srcElement || e.target; // unset these straight away so we don't call this twice img.onload = img.onerror = null; // pull it back out of the (hidden) DOM // so that draw will add it correctly later theManager.loadingBay.removeChild(img); theManager.openRequestCount--; delete theManager.requestsById[img.id]; /* console.log(theManager.openRequestCount + ' open requests'); */ // NB:- complete is also true onerror if we got a 404 if (e.type === 'load' && (img.complete || (img.readyState && img.readyState == 'complete'))) { theManager.dispatchCallback('requestcomplete', img); } else { // if it didn't finish clear its src to make sure it // really stops loading // FIXME: we'll never retry because this id is still // in requestsById - is that right? theManager.dispatchCallback('requesterror', { element: img, url: ('' + img.src) }); img.src = null; } // keep going in the same order // use `setTimeout()` to avoid the IE recursion limit, see // http://cappuccino.org/discuss/2010/03/01/internet-explorer-global-variables-and-stack-overflows/ // and https://github.com/stamen/modestmaps-js/issues/12 setTimeout(theManager.getProcessQueue(), 0); }; } return this._loadComplete; } }; modestmaps-js-3.3.6/src/start.js000066400000000000000000000011241210227260500165660ustar00rootroot00000000000000/*! * Modest Maps JS v{VERSION} * http://modestmaps.com/ * * Copyright (c) 2011 Stamen Design, All Rights Reserved. * * Open source under the BSD License. * http://creativecommons.org/licenses/BSD/ * * Versioned using Semantic Versioning (v.major.minor.patch) * See CHANGELOG and http://semver.org/ for more details. * */ var previousMM = MM; // namespacing for backwards-compatibility if (!com) { var com = {}; if (!com.modestmaps) com.modestmaps = {}; } var MM = com.modestmaps = { noConflict: function() { MM = previousMM; return this; } }; (function(MM) { modestmaps-js-3.3.6/src/touch.js000066400000000000000000000166001210227260500165600ustar00rootroot00000000000000 MM.TouchHandler = function() { var handler = {}, map, maxTapTime = 250, maxTapDistance = 30, maxDoubleTapDelay = 350, locations = {}, taps = [], snapToZoom = true, wasPinching = false, lastPinchCenter = null; function isTouchable () { var el = document.createElement('div'); el.setAttribute('ongesturestart', 'return;'); return (typeof el.ongesturestart === 'function'); } function updateTouches(e) { for (var i = 0; i < e.touches.length; i += 1) { var t = e.touches[i]; if (t.identifier in locations) { var l = locations[t.identifier]; l.x = t.clientX; l.y = t.clientY; l.scale = e.scale; } else { locations[t.identifier] = { scale: e.scale, startPos: { x: t.clientX, y: t.clientY }, x: t.clientX, y: t.clientY, time: new Date().getTime() }; } } } // Test whether touches are from the same source - // whether this is the same touchmove event. function sameTouch (event, touch) { return (event && event.touch) && (touch.identifier == event.touch.identifier); } function touchStart(e) { updateTouches(e); } function touchMove(e) { switch (e.touches.length) { case 1: onPanning(e.touches[0]); break; case 2: onPinching(e); break; } updateTouches(e); return MM.cancelEvent(e); } function touchEnd(e) { var now = new Date().getTime(); // round zoom if we're done pinching if (e.touches.length === 0 && wasPinching) { onPinched(lastPinchCenter); } // Look at each changed touch in turn. for (var i = 0; i < e.changedTouches.length; i += 1) { var t = e.changedTouches[i], loc = locations[t.identifier]; // if we didn't see this one (bug?) // or if it was consumed by pinching already // just skip to the next one if (!loc || loc.wasPinch) { continue; } // we now know we have an event object and a // matching touch that's just ended. Let's see // what kind of event it is based on how long it // lasted and how far it moved. var pos = { x: t.clientX, y: t.clientY }, time = now - loc.time, travel = MM.Point.distance(pos, loc.startPos); if (travel > maxTapDistance) { // we will to assume that the drag has been handled separately } else if (time > maxTapTime) { // close in space, but not in time: a hold pos.end = now; pos.duration = time; onHold(pos); } else { // close in both time and space: a tap pos.time = now; onTap(pos); } } // Weird, sometimes an end event doesn't get thrown // for a touch that nevertheless has disappeared. // Still, this will eventually catch those ids: var validTouchIds = {}; for (var j = 0; j < e.touches.length; j++) { validTouchIds[e.touches[j].identifier] = true; } for (var id in locations) { if (!(id in validTouchIds)) { delete validTouchIds[id]; } } return MM.cancelEvent(e); } function onHold (hold) { // TODO } // Handle a tap event - mainly watch for a doubleTap function onTap(tap) { if (taps.length && (tap.time - taps[0].time) < maxDoubleTapDelay) { onDoubleTap(tap); taps = []; return; } taps = [tap]; } // Handle a double tap by zooming in a single zoom level to a // round zoom. function onDoubleTap(tap) { var z = map.getZoom(), // current zoom tz = Math.round(z) + 1, // target zoom dz = tz - z; // desired delate // zoom in to a round number var p = new MM.Point(tap.x, tap.y); map.zoomByAbout(dz, p); } // Re-transform the actual map parent's CSS transformation function onPanning (touch) { var pos = { x: touch.clientX, y: touch.clientY }, prev = locations[touch.identifier]; map.panBy(pos.x - prev.x, pos.y - prev.y); } function onPinching(e) { // use the first two touches and their previous positions var t0 = e.touches[0], t1 = e.touches[1], p0 = new MM.Point(t0.clientX, t0.clientY), p1 = new MM.Point(t1.clientX, t1.clientY), l0 = locations[t0.identifier], l1 = locations[t1.identifier]; // mark these touches so they aren't used as taps/holds l0.wasPinch = true; l1.wasPinch = true; // scale about the center of these touches var center = MM.Point.interpolate(p0, p1, 0.5); map.zoomByAbout( Math.log(e.scale) / Math.LN2 - Math.log(l0.scale) / Math.LN2, center ); // pan from the previous center of these touches var prevCenter = MM.Point.interpolate(l0, l1, 0.5); map.panBy(center.x - prevCenter.x, center.y - prevCenter.y); wasPinching = true; lastPinchCenter = center; } // When a pinch event ends, round the zoom of the map. function onPinched(p) { // TODO: easing if (snapToZoom) { var z = map.getZoom(), // current zoom tz =Math.round(z); // target zoom map.zoomByAbout(tz - z, p); } wasPinching = false; } handler.init = function(x) { map = x; // Fail early if this isn't a touch device. if (!isTouchable()) return handler; MM.addEvent(map.parent, 'touchstart', touchStart); MM.addEvent(map.parent, 'touchmove', touchMove); MM.addEvent(map.parent, 'touchend', touchEnd); return handler; }; handler.remove = function() { // Fail early if this isn't a touch device. if (!isTouchable()) return handler; MM.removeEvent(map.parent, 'touchstart', touchStart); MM.removeEvent(map.parent, 'touchmove', touchMove); MM.removeEvent(map.parent, 'touchend', touchEnd); return handler; }; return handler; }; modestmaps-js-3.3.6/src/transformation.js000066400000000000000000000052741210227260500205110ustar00rootroot00000000000000 // Transformation // -------------- MM.Transformation = function(ax, bx, cx, ay, by, cy) { this.ax = ax; this.bx = bx; this.cx = cx; this.ay = ay; this.by = by; this.cy = cy; }; MM.Transformation.prototype = { ax: 0, bx: 0, cx: 0, ay: 0, by: 0, cy: 0, transform: function(point) { return new MM.Point(this.ax * point.x + this.bx * point.y + this.cx, this.ay * point.x + this.by * point.y + this.cy); }, untransform: function(point) { return new MM.Point((point.x * this.by - point.y * this.bx - this.cx * this.by + this.cy * this.bx) / (this.ax * this.by - this.ay * this.bx), (point.x * this.ay - point.y * this.ax - this.cx * this.ay + this.cy * this.ax) / (this.bx * this.ay - this.by * this.ax)); } }; // Generates a transform based on three pairs of points, // a1 -> a2, b1 -> b2, c1 -> c2. MM.deriveTransformation = function(a1x, a1y, a2x, a2y, b1x, b1y, b2x, b2y, c1x, c1y, c2x, c2y) { var x = MM.linearSolution(a1x, a1y, a2x, b1x, b1y, b2x, c1x, c1y, c2x); var y = MM.linearSolution(a1x, a1y, a2y, b1x, b1y, b2y, c1x, c1y, c2y); return new MM.Transformation(x[0], x[1], x[2], y[0], y[1], y[2]); }; // Solves a system of linear equations. // // t1 = (a * r1) + (b + s1) + c // t2 = (a * r2) + (b + s2) + c // t3 = (a * r3) + (b + s3) + c // // r1 - t3 are the known values. // a, b, c are the unknowns to be solved. // returns the a, b, c coefficients. MM.linearSolution = function(r1, s1, t1, r2, s2, t2, r3, s3, t3) { // make them all floats r1 = parseFloat(r1); s1 = parseFloat(s1); t1 = parseFloat(t1); r2 = parseFloat(r2); s2 = parseFloat(s2); t2 = parseFloat(t2); r3 = parseFloat(r3); s3 = parseFloat(s3); t3 = parseFloat(t3); var a = (((t2 - t3) * (s1 - s2)) - ((t1 - t2) * (s2 - s3))) / (((r2 - r3) * (s1 - s2)) - ((r1 - r2) * (s2 - s3))); var b = (((t2 - t3) * (r1 - r2)) - ((t1 - t2) * (r2 - r3))) / (((s2 - s3) * (r1 - r2)) - ((s1 - s2) * (r2 - r3))); var c = t1 - (r1 * a) - (s1 * b); return [ a, b, c ]; }; modestmaps-js-3.3.6/src/utils.js000066400000000000000000000136501210227260500166000ustar00rootroot00000000000000 // Make inheritance bearable: clone one level of properties MM.extend = function(child, parent) { for (var property in parent.prototype) { if (typeof child.prototype[property] == "undefined") { child.prototype[property] = parent.prototype[property]; } } return child; }; MM.getFrame = function () { // native animation frames // http://webstuff.nfshost.com/anim-timing/Overview.html // http://dev.chromium.org/developers/design-documents/requestanimationframe-implementation // http://paulirish.com/2011/requestanimationframe-for-smart-animating/ // can't apply these directly to MM because Chrome needs window // to own webkitRequestAnimationFrame (for example) // perhaps we should namespace an alias onto window instead? // e.g. window.mmRequestAnimationFrame? return function(callback) { (window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) { window.setTimeout(function () { callback(+new Date()); }, 10); })(callback); }; }(); // Inspired by LeafletJS MM.transformProperty = (function(props) { if (!this.document) return; // node.js safety var style = document.documentElement.style; for (var i = 0; i < props.length; i++) { if (props[i] in style) { return props[i]; } } return false; })(['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']); MM.matrixString = function(point) { // Make the result of point.scale * point.width a whole number. if (point.scale * point.width % 1) { point.scale += (1 - point.scale * point.width % 1) / point.width; } var scale = point.scale || 1; if (MM._browser.webkit3d) { return 'translate3d(' + point.x.toFixed(0) + 'px,' + point.y.toFixed(0) + 'px, 0px)' + 'scale3d(' + scale + ',' + scale + ', 1)'; } else { return 'translate(' + point.x.toFixed(6) + 'px,' + point.y.toFixed(6) + 'px)' + 'scale(' + scale + ',' + scale + ')'; } }; MM._browser = (function(window) { return { webkit: ('WebKitCSSMatrix' in window), webkit3d: ('WebKitCSSMatrix' in window) && ('m11' in new WebKitCSSMatrix()) }; })(this); // use this for node.js global MM.moveElement = function(el, point) { if (MM.transformProperty) { // Optimize for identity transforms, where you don't actually // need to change this element's string. Browsers can optimize for // the .style.left case but not for this CSS case. if (!point.scale) point.scale = 1; if (!point.width) point.width = 0; if (!point.height) point.height = 0; var ms = MM.matrixString(point); if (el[MM.transformProperty] !== ms) { el.style[MM.transformProperty] = el[MM.transformProperty] = ms; } } else { el.style.left = point.x + 'px'; el.style.top = point.y + 'px'; // Don't set width unless asked to: this is performance-intensive // and not always necessary if (point.width && point.height && point.scale) { el.style.width = Math.ceil(point.width * point.scale) + 'px'; el.style.height = Math.ceil(point.height * point.scale) + 'px'; } } }; // Events // Cancel an event: prevent it from bubbling MM.cancelEvent = function(e) { // there's more than one way to skin this cat e.cancelBubble = true; e.cancel = true; e.returnValue = false; if (e.stopPropagation) { e.stopPropagation(); } if (e.preventDefault) { e.preventDefault(); } return false; }; MM.coerceLayer = function(layerish) { if (typeof layerish == 'string') { // Probably a template string return new MM.Layer(new MM.TemplatedLayer(layerish)); } else if ('draw' in layerish && typeof layerish.draw == 'function') { // good enough, though we should probably enforce .parent and .destroy() too return layerish; } else { // probably a MapProvider return new MM.Layer(layerish); } }; // see http://ejohn.org/apps/jselect/event.html for the originals MM.addEvent = function(obj, type, fn) { if (obj.addEventListener) { obj.addEventListener(type, fn, false); if (type == 'mousewheel') { obj.addEventListener('DOMMouseScroll', fn, false); } } else if (obj.attachEvent) { obj['e'+type+fn] = fn; obj[type+fn] = function(){ obj['e'+type+fn](window.event); }; obj.attachEvent('on'+type, obj[type+fn]); } }; MM.removeEvent = function( obj, type, fn ) { if (obj.removeEventListener) { obj.removeEventListener(type, fn, false); if (type == 'mousewheel') { obj.removeEventListener('DOMMouseScroll', fn, false); } } else if (obj.detachEvent) { obj.detachEvent('on'+type, obj[type+fn]); obj[type+fn] = null; } }; // Cross-browser function to get current element style property MM.getStyle = function(el,styleProp) { if (el.currentStyle) return el.currentStyle[styleProp]; else if (window.getComputedStyle) return document.defaultView.getComputedStyle(el,null).getPropertyValue(styleProp); }; modestmaps-js-3.3.6/test/000077500000000000000000000000001210227260500152655ustar00rootroot00000000000000modestmaps-js-3.3.6/test/Globals.js000066400000000000000000000014121210227260500172040ustar00rootroot00000000000000describe('Globals', function() { it('does not leak', function() { console.log('here'); var globalsBefore = {}; for (var key in window) { globalsBefore[key] = true; } var div = document.createElement('div'); div.id = +new Date(); div.style.width = 500; div.style.height = 500; var template = 'http://{S}tile.openstreetmap.org/{Z}/{X}/{Y}.png'; var subdomains = [ '', 'a.', 'b.', 'c.' ]; var provider = new MM.TemplatedLayer(template, subdomains); var map = new MM.Map(div, provider, new MM.Point(400, 400)); var globalsAfter = {}; for (var afterkey in window) { globalsAfter[afterkey] = true; } expect(globalsBefore).toEqual(globalsAfter); }); }); modestmaps-js-3.3.6/test/index.html000066400000000000000000000043341210227260500172660ustar00rootroot00000000000000 Jasmine Spec Runner modestmaps-js-3.3.6/test/lib/000077500000000000000000000000001210227260500160335ustar00rootroot00000000000000modestmaps-js-3.3.6/test/lib/happen.js000066400000000000000000000030251210227260500176440ustar00rootroot00000000000000!(function(context) { var h = {}; // Make inheritance bearable: clone one level of properties function extend(child, parent) { for (var property in parent) { if (typeof child[property] == 'undefined') { child[property] = parent[property]; } } return child; } h.once = function(x, o) { var evt = document.createEvent('MouseEvents'); // https://developer.mozilla.org/en/DOM/event.initMouseEvent evt.initMouseEvent(o.type, true, // canBubble true, // cancelable window, // 'AbstractView' o.detail || 0, // click count o.screenX || 0, // screenX o.screenY || 0, // screenY o.clientX || 0, // clientX o.clientY || 0, // clientY o.ctrl || 0, // ctrl o.alt || false, // alt o.shift || false, // shift o.meta || false, // meta o.button || false, // mouse button null // relatedTarget ); x.dispatchEvent(evt); }; var shortcuts = ['click', 'mousedown', 'mouseup', 'mousemove'], s, i = 0; while (s = shortcuts[i++]) { h[s] = (function(s) { return function(x, o) { h.once(x, extend(o || {}, { type: s })); }; })(s); } h.dblclick = function(x, o) { h.once(x, extend(o || {}, { type: 'dblclick', detail: 2 })); }; this.happen = h; })(this); modestmaps-js-3.3.6/test/lib/jasmine-1.1.0.rc1/000077500000000000000000000000001210227260500206005ustar00rootroot00000000000000modestmaps-js-3.3.6/test/lib/jasmine-1.1.0.rc1/MIT.LICENSE000066400000000000000000000020451210227260500222360ustar00rootroot00000000000000Copyright (c) 2008-2011 Pivotal Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. modestmaps-js-3.3.6/test/lib/jasmine-1.1.0.rc1/jasmine-html.js000066400000000000000000000154261210227260500235360ustar00rootroot00000000000000jasmine.TrivialReporter = function(doc) { this.document = doc || document; this.suiteDivs = {}; this.logRunningSpecs = false; }; jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) { var el = document.createElement(type); for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child === 'string') { el.appendChild(document.createTextNode(child)); } else { if (child) { el.appendChild(child); } } } for (var attr in attrs) { if (attr == "className") { el[attr] = attrs[attr]; } else { el.setAttribute(attr, attrs[attr]); } } return el; }; jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) { var showPassed, showSkipped; this.outerDiv = this.createDom('div', { className: 'jasmine_reporter' }, this.createDom('div', { className: 'banner' }, this.createDom('div', { className: 'logo' }, this.createDom('span', { className: 'title' }, "Jasmine"), this.createDom('span', { className: 'version' }, runner.env.versionString())), this.createDom('div', { className: 'options' }, "Show ", showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }), this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "), showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }), this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped") ) ), this.runnerDiv = this.createDom('div', { className: 'runner running' }, this.createDom('a', { className: 'run_spec', href: '?' }, "run all"), this.runnerMessageSpan = this.createDom('span', {}, "Running..."), this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, "")) ); this.document.body.appendChild(this.outerDiv); var suites = runner.suites(); for (var i = 0; i < suites.length; i++) { var suite = suites[i]; var suiteDiv = this.createDom('div', { className: 'suite' }, this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"), this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description)); this.suiteDivs[suite.id] = suiteDiv; var parentDiv = this.outerDiv; if (suite.parentSuite) { parentDiv = this.suiteDivs[suite.parentSuite.id]; } parentDiv.appendChild(suiteDiv); } this.startedAt = new Date(); var self = this; showPassed.onclick = function(evt) { if (showPassed.checked) { self.outerDiv.className += ' show-passed'; } else { self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, ''); } }; showSkipped.onclick = function(evt) { if (showSkipped.checked) { self.outerDiv.className += ' show-skipped'; } else { self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, ''); } }; }; jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) { var results = runner.results(); var className = (results.failedCount > 0) ? "runner failed" : "runner passed"; this.runnerDiv.setAttribute("class", className); //do it twice for IE this.runnerDiv.setAttribute("className", className); var specs = runner.specs(); var specCount = 0; for (var i = 0; i < specs.length; i++) { if (this.specFilter(specs[i])) { specCount++; } } var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s"); message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"; this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild); this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString())); }; jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) { var results = suite.results(); var status = results.passed() ? 'passed' : 'failed'; if (results.totalCount === 0) { // todo: change this to check results.skipped status = 'skipped'; } this.suiteDivs[suite.id].className += " " + status; }; jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) { if (this.logRunningSpecs) { this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); } }; jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) { var results = spec.results(); var status = results.passed() ? 'passed' : 'failed'; if (results.skipped) { status = 'skipped'; } var specDiv = this.createDom('div', { className: 'spec ' + status }, this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"), this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(spec.getFullName()), title: spec.getFullName() }, spec.description)); var resultItems = results.getItems(); var messagesDiv = this.createDom('div', { className: 'messages' }); for (var i = 0; i < resultItems.length; i++) { var result = resultItems[i]; if (result.type == 'log') { messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); } else if (result.type == 'expect' && result.passed && !result.passed()) { messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); if (result.trace.stack) { messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); } } } if (messagesDiv.childNodes.length > 0) { specDiv.appendChild(messagesDiv); } this.suiteDivs[spec.suite.id].appendChild(specDiv); }; jasmine.TrivialReporter.prototype.log = function() { var console = jasmine.getGlobal().console; if (console && console.log) { if (console.log.apply) { console.log.apply(console, arguments); } else { console.log(arguments); // ie fix: console.log.apply doesn't exist on ie } } }; jasmine.TrivialReporter.prototype.getLocation = function() { return this.document.location; }; jasmine.TrivialReporter.prototype.specFilter = function(spec) { var paramMap = {}; var params = this.getLocation().search.substring(1).split('&'); for (var i = 0; i < params.length; i++) { var p = params[i].split('='); paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); } if (!paramMap.spec) { return true; } return spec.getFullName().indexOf(paramMap.spec) === 0; }; modestmaps-js-3.3.6/test/lib/jasmine-1.1.0.rc1/jasmine.css000066400000000000000000000041101210227260500227340ustar00rootroot00000000000000body { font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; } .jasmine_reporter a:visited, .jasmine_reporter a { color: #303; } .jasmine_reporter a:hover, .jasmine_reporter a:active { color: blue; } .run_spec { float:right; padding-right: 5px; font-size: .8em; text-decoration: none; } .jasmine_reporter { margin: 0 5px; } .banner { color: #303; background-color: #fef; padding: 5px; } .logo { float: left; font-size: 1.1em; padding-left: 5px; } .logo .version { font-size: .6em; padding-left: 1em; } .runner.running { background-color: yellow; } .options { text-align: right; font-size: .8em; } .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; } .suite .suite { margin: 5px; } .suite.passed { background-color: #dfd; } .suite.failed { background-color: #fdd; } .spec { margin: 5px; padding-left: 1em; clear: both; } .spec.failed, .spec.passed, .spec.skipped { padding-bottom: 5px; border: 1px solid gray; } .spec.failed { background-color: #fbb; border-color: red; } .spec.passed { background-color: #bfb; border-color: green; } .spec.skipped { background-color: #bbb; } .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; } .passed { background-color: #cfc; display: none; } .failed { background-color: #fbb; } .skipped { color: #777; background-color: #eee; display: none; } /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ .resultMessage span.result { display: block; line-height: 2em; color: black; } .resultMessage .mismatch { color: black; } .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; } .finished-at { padding-left: 1em; font-size: .6em; } .show-passed .passed, .show-skipped .skipped { display: block; } #jasmine_content { position:fixed; right: 100%; } .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; } modestmaps-js-3.3.6/test/lib/jasmine-1.1.0.rc1/jasmine.js000066400000000000000000002022411210227260500225650ustar00rootroot00000000000000var isCommonJS = typeof window == "undefined"; /** * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework. * * @namespace */ var jasmine = {}; if (isCommonJS) exports.jasmine = jasmine; /** * @private */ jasmine.unimplementedMethod_ = function() { throw new Error("unimplemented method"); }; /** * Use jasmine.undefined instead of undefined, since undefined is just * a plain old variable and may be redefined by somebody else. * * @private */ jasmine.undefined = jasmine.___undefined___; /** * Show diagnostic messages in the console if set to true * */ jasmine.VERBOSE = false; /** * Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed. * */ jasmine.DEFAULT_UPDATE_INTERVAL = 250; /** * Default timeout interval in milliseconds for waitsFor() blocks. */ jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000; jasmine.getGlobal = function() { function getGlobal() { return this; } return getGlobal(); }; /** * Allows for bound functions to be compared. Internal use only. * * @ignore * @private * @param base {Object} bound 'this' for the function * @param name {Function} function to find */ jasmine.bindOriginal_ = function(base, name) { var original = base[name]; if (original.apply) { return function() { return original.apply(base, arguments); }; } else { // IE support return jasmine.getGlobal()[name]; } }; jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout'); jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout'); jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval'); jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval'); jasmine.MessageResult = function(values) { this.type = 'log'; this.values = values; this.trace = new Error(); // todo: test better }; jasmine.MessageResult.prototype.toString = function() { var text = ""; for (var i = 0; i < this.values.length; i++) { if (i > 0) text += " "; if (jasmine.isString_(this.values[i])) { text += this.values[i]; } else { text += jasmine.pp(this.values[i]); } } return text; }; jasmine.ExpectationResult = function(params) { this.type = 'expect'; this.matcherName = params.matcherName; this.passed_ = params.passed; this.expected = params.expected; this.actual = params.actual; this.message = this.passed_ ? 'Passed.' : params.message; var trace = (params.trace || new Error(this.message)); this.trace = this.passed_ ? '' : trace; }; jasmine.ExpectationResult.prototype.toString = function () { return this.message; }; jasmine.ExpectationResult.prototype.passed = function () { return this.passed_; }; /** * Getter for the Jasmine environment. Ensures one gets created */ jasmine.getEnv = function() { var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env(); return env; }; /** * @ignore * @private * @param value * @returns {Boolean} */ jasmine.isArray_ = function(value) { return jasmine.isA_("Array", value); }; /** * @ignore * @private * @param value * @returns {Boolean} */ jasmine.isString_ = function(value) { return jasmine.isA_("String", value); }; /** * @ignore * @private * @param value * @returns {Boolean} */ jasmine.isNumber_ = function(value) { return jasmine.isA_("Number", value); }; /** * @ignore * @private * @param {String} typeName * @param value * @returns {Boolean} */ jasmine.isA_ = function(typeName, value) { return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; }; /** * Pretty printer for expecations. Takes any object and turns it into a human-readable string. * * @param value {Object} an object to be outputted * @returns {String} */ jasmine.pp = function(value) { var stringPrettyPrinter = new jasmine.StringPrettyPrinter(); stringPrettyPrinter.format(value); return stringPrettyPrinter.string; }; /** * Returns true if the object is a DOM Node. * * @param {Object} obj object to check * @returns {Boolean} */ jasmine.isDomNode = function(obj) { return obj.nodeType > 0; }; /** * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter. * * @example * // don't care about which function is passed in, as long as it's a function * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function)); * * @param {Class} clazz * @returns matchable object of the type clazz */ jasmine.any = function(clazz) { return new jasmine.Matchers.Any(clazz); }; /** * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks. * * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine * expectation syntax. Spies can be checked if they were called or not and what the calling params were. * * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs). * * Spies are torn down at the end of every spec. * * Note: Do not call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj. * * @example * // a stub * var myStub = jasmine.createSpy('myStub'); // can be used anywhere * * // spy example * var foo = { * not: function(bool) { return !bool; } * } * * // actual foo.not will not be called, execution stops * spyOn(foo, 'not'); // foo.not spied upon, execution will continue to implementation * spyOn(foo, 'not').andCallThrough(); * * // fake example * var foo = { * not: function(bool) { return !bool; } * } * * // foo.not(val) will return val * spyOn(foo, 'not').andCallFake(function(value) {return value;}); * * // mock example * foo.not(7 == 7); * expect(foo.not).toHaveBeenCalled(); * expect(foo.not).toHaveBeenCalledWith(true); * * @constructor * @see spyOn, jasmine.createSpy, jasmine.createSpyObj * @param {String} name */ jasmine.Spy = function(name) { /** * The name of the spy, if provided. */ this.identity = name || 'unknown'; /** * Is this Object a spy? */ this.isSpy = true; /** * The actual function this spy stubs. */ this.plan = function() { }; /** * Tracking of the most recent call to the spy. * @example * var mySpy = jasmine.createSpy('foo'); * mySpy(1, 2); * mySpy.mostRecentCall.args = [1, 2]; */ this.mostRecentCall = {}; /** * Holds arguments for each call to the spy, indexed by call count * @example * var mySpy = jasmine.createSpy('foo'); * mySpy(1, 2); * mySpy(7, 8); * mySpy.mostRecentCall.args = [7, 8]; * mySpy.argsForCall[0] = [1, 2]; * mySpy.argsForCall[1] = [7, 8]; */ this.argsForCall = []; this.calls = []; }; /** * Tells a spy to call through to the actual implemenatation. * * @example * var foo = { * bar: function() { // do some stuff } * } * * // defining a spy on an existing property: foo.bar * spyOn(foo, 'bar').andCallThrough(); */ jasmine.Spy.prototype.andCallThrough = function() { this.plan = this.originalValue; return this; }; /** * For setting the return value of a spy. * * @example * // defining a spy from scratch: foo() returns 'baz' * var foo = jasmine.createSpy('spy on foo').andReturn('baz'); * * // defining a spy on an existing property: foo.bar() returns 'baz' * spyOn(foo, 'bar').andReturn('baz'); * * @param {Object} value */ jasmine.Spy.prototype.andReturn = function(value) { this.plan = function() { return value; }; return this; }; /** * For throwing an exception when a spy is called. * * @example * // defining a spy from scratch: foo() throws an exception w/ message 'ouch' * var foo = jasmine.createSpy('spy on foo').andThrow('baz'); * * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch' * spyOn(foo, 'bar').andThrow('baz'); * * @param {String} exceptionMsg */ jasmine.Spy.prototype.andThrow = function(exceptionMsg) { this.plan = function() { throw exceptionMsg; }; return this; }; /** * Calls an alternate implementation when a spy is called. * * @example * var baz = function() { * // do some stuff, return something * } * // defining a spy from scratch: foo() calls the function baz * var foo = jasmine.createSpy('spy on foo').andCall(baz); * * // defining a spy on an existing property: foo.bar() calls an anonymnous function * spyOn(foo, 'bar').andCall(function() { return 'baz';} ); * * @param {Function} fakeFunc */ jasmine.Spy.prototype.andCallFake = function(fakeFunc) { this.plan = fakeFunc; return this; }; /** * Resets all of a spy's the tracking variables so that it can be used again. * * @example * spyOn(foo, 'bar'); * * foo.bar(); * * expect(foo.bar.callCount).toEqual(1); * * foo.bar.reset(); * * expect(foo.bar.callCount).toEqual(0); */ jasmine.Spy.prototype.reset = function() { this.wasCalled = false; this.callCount = 0; this.argsForCall = []; this.calls = []; this.mostRecentCall = {}; }; jasmine.createSpy = function(name) { var spyObj = function() { spyObj.wasCalled = true; spyObj.callCount++; var args = jasmine.util.argsToArray(arguments); spyObj.mostRecentCall.object = this; spyObj.mostRecentCall.args = args; spyObj.argsForCall.push(args); spyObj.calls.push({object: this, args: args}); return spyObj.plan.apply(this, arguments); }; var spy = new jasmine.Spy(name); for (var prop in spy) { spyObj[prop] = spy[prop]; } spyObj.reset(); return spyObj; }; /** * Determines whether an object is a spy. * * @param {jasmine.Spy|Object} putativeSpy * @returns {Boolean} */ jasmine.isSpy = function(putativeSpy) { return putativeSpy && putativeSpy.isSpy; }; /** * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something * large in one call. * * @param {String} baseName name of spy class * @param {Array} methodNames array of names of methods to make spies */ jasmine.createSpyObj = function(baseName, methodNames) { if (!jasmine.isArray_(methodNames) || methodNames.length === 0) { throw new Error('createSpyObj requires a non-empty array of method names to create spies for'); } var obj = {}; for (var i = 0; i < methodNames.length; i++) { obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]); } return obj; }; /** * All parameters are pretty-printed and concatenated together, then written to the current spec's output. * * Be careful not to leave calls to jasmine.log in production code. */ jasmine.log = function() { var spec = jasmine.getEnv().currentSpec; spec.log.apply(spec, arguments); }; /** * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy. * * @example * // spy example * var foo = { * not: function(bool) { return !bool; } * } * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops * * @see jasmine.createSpy * @param obj * @param methodName * @returns a Jasmine spy that can be chained with all spy methods */ var spyOn = function(obj, methodName) { return jasmine.getEnv().currentSpec.spyOn(obj, methodName); }; if (isCommonJS) exports.spyOn = spyOn; /** * Creates a Jasmine spec that will be added to the current suite. * * // TODO: pending tests * * @example * it('should be true', function() { * expect(true).toEqual(true); * }); * * @param {String} desc description of this specification * @param {Function} func defines the preconditions and expectations of the spec */ var it = function(desc, func) { return jasmine.getEnv().it(desc, func); }; if (isCommonJS) exports.it = it; /** * Creates a disabled Jasmine spec. * * A convenience method that allows existing specs to be disabled temporarily during development. * * @param {String} desc description of this specification * @param {Function} func defines the preconditions and expectations of the spec */ var xit = function(desc, func) { return jasmine.getEnv().xit(desc, func); }; if (isCommonJS) exports.xit = xit; /** * Starts a chain for a Jasmine expectation. * * It is passed an Object that is the actual value and should chain to one of the many * jasmine.Matchers functions. * * @param {Object} actual Actual value to test against and expected value */ var expect = function(actual) { return jasmine.getEnv().currentSpec.expect(actual); }; if (isCommonJS) exports.expect = expect; /** * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs. * * @param {Function} func Function that defines part of a jasmine spec. */ var runs = function(func) { jasmine.getEnv().currentSpec.runs(func); }; if (isCommonJS) exports.runs = runs; /** * Waits a fixed time period before moving to the next block. * * @deprecated Use waitsFor() instead * @param {Number} timeout milliseconds to wait */ var waits = function(timeout) { jasmine.getEnv().currentSpec.waits(timeout); }; if (isCommonJS) exports.waits = waits; /** * Waits for the latchFunction to return true before proceeding to the next block. * * @param {Function} latchFunction * @param {String} optional_timeoutMessage * @param {Number} optional_timeout */ var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments); }; if (isCommonJS) exports.waitsFor = waitsFor; /** * A function that is called before each spec in a suite. * * Used for spec setup, including validating assumptions. * * @param {Function} beforeEachFunction */ var beforeEach = function(beforeEachFunction) { jasmine.getEnv().beforeEach(beforeEachFunction); }; if (isCommonJS) exports.beforeEach = beforeEach; /** * A function that is called after each spec in a suite. * * Used for restoring any state that is hijacked during spec execution. * * @param {Function} afterEachFunction */ var afterEach = function(afterEachFunction) { jasmine.getEnv().afterEach(afterEachFunction); }; if (isCommonJS) exports.afterEach = afterEach; /** * Defines a suite of specifications. * * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization * of setup in some tests. * * @example * // TODO: a simple suite * * // TODO: a simple suite with a nested describe block * * @param {String} description A string, usually the class under test. * @param {Function} specDefinitions function that defines several specs. */ var describe = function(description, specDefinitions) { return jasmine.getEnv().describe(description, specDefinitions); }; if (isCommonJS) exports.describe = describe; /** * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development. * * @param {String} description A string, usually the class under test. * @param {Function} specDefinitions function that defines several specs. */ var xdescribe = function(description, specDefinitions) { return jasmine.getEnv().xdescribe(description, specDefinitions); }; if (isCommonJS) exports.xdescribe = xdescribe; // Provide the XMLHttpRequest class for IE 5.x-6.x: jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() { function tryIt(f) { try { return f(); } catch(e) { } return null; } var xhr = tryIt(function() { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); }) || tryIt(function() { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); }) || tryIt(function() { return new ActiveXObject("Msxml2.XMLHTTP"); }) || tryIt(function() { return new ActiveXObject("Microsoft.XMLHTTP"); }); if (!xhr) throw new Error("This browser does not support XMLHttpRequest."); return xhr; } : XMLHttpRequest; /** * @namespace */ jasmine.util = {}; /** * Declare that a child class inherit it's prototype from the parent class. * * @private * @param {Function} childClass * @param {Function} parentClass */ jasmine.util.inherit = function(childClass, parentClass) { /** * @private */ var subclass = function() { }; subclass.prototype = parentClass.prototype; childClass.prototype = new subclass(); }; jasmine.util.formatException = function(e) { var lineNumber; if (e.line) { lineNumber = e.line; } else if (e.lineNumber) { lineNumber = e.lineNumber; } var file; if (e.sourceURL) { file = e.sourceURL; } else if (e.fileName) { file = e.fileName; } var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString(); if (file && lineNumber) { message += ' in ' + file + ' (line ' + lineNumber + ')'; } return message; }; jasmine.util.htmlEscape = function(str) { if (!str) return str; return str.replace(/&/g, '&') .replace(//g, '>'); }; jasmine.util.argsToArray = function(args) { var arrayOfArgs = []; for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]); return arrayOfArgs; }; jasmine.util.extend = function(destination, source) { for (var property in source) destination[property] = source[property]; return destination; }; /** * Environment for Jasmine * * @constructor */ jasmine.Env = function() { this.currentSpec = null; this.currentSuite = null; this.currentRunner_ = new jasmine.Runner(this); this.reporter = new jasmine.MultiReporter(); this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL; this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL; this.lastUpdate = 0; this.specFilter = function() { return true; }; this.nextSpecId_ = 0; this.nextSuiteId_ = 0; this.equalityTesters_ = []; // wrap matchers this.matchersClass = function() { jasmine.Matchers.apply(this, arguments); }; jasmine.util.inherit(this.matchersClass, jasmine.Matchers); jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass); }; jasmine.Env.prototype.setTimeout = jasmine.setTimeout; jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout; jasmine.Env.prototype.setInterval = jasmine.setInterval; jasmine.Env.prototype.clearInterval = jasmine.clearInterval; /** * @returns an object containing jasmine version build info, if set. */ jasmine.Env.prototype.version = function () { if (jasmine.version_) { return jasmine.version_; } else { throw new Error('Version not set'); } }; /** * @returns string containing jasmine version build info, if set. */ jasmine.Env.prototype.versionString = function() { if (!jasmine.version_) { return "version unknown"; } var version = this.version(); var dotted_version = version.major + "." + version.minor + "." + version.build; if (version.rc) { dotted_version += ".rc" + version.rc; } return dotted_version + " revision " + version.revision; }; /** * @returns a sequential integer starting at 0 */ jasmine.Env.prototype.nextSpecId = function () { return this.nextSpecId_++; }; /** * @returns a sequential integer starting at 0 */ jasmine.Env.prototype.nextSuiteId = function () { return this.nextSuiteId_++; }; /** * Register a reporter to receive status updates from Jasmine. * @param {jasmine.Reporter} reporter An object which will receive status updates. */ jasmine.Env.prototype.addReporter = function(reporter) { this.reporter.addReporter(reporter); }; jasmine.Env.prototype.execute = function() { this.currentRunner_.execute(); }; jasmine.Env.prototype.describe = function(description, specDefinitions) { var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite); var parentSuite = this.currentSuite; if (parentSuite) { parentSuite.add(suite); } else { this.currentRunner_.add(suite); } this.currentSuite = suite; var declarationError = null; try { specDefinitions.call(suite); } catch(e) { declarationError = e; } if (declarationError) { this.it("encountered a declaration exception", function() { throw declarationError; }); } this.currentSuite = parentSuite; return suite; }; jasmine.Env.prototype.beforeEach = function(beforeEachFunction) { if (this.currentSuite) { this.currentSuite.beforeEach(beforeEachFunction); } else { this.currentRunner_.beforeEach(beforeEachFunction); } }; jasmine.Env.prototype.currentRunner = function () { return this.currentRunner_; }; jasmine.Env.prototype.afterEach = function(afterEachFunction) { if (this.currentSuite) { this.currentSuite.afterEach(afterEachFunction); } else { this.currentRunner_.afterEach(afterEachFunction); } }; jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) { return { execute: function() { } }; }; jasmine.Env.prototype.it = function(description, func) { var spec = new jasmine.Spec(this, this.currentSuite, description); this.currentSuite.add(spec); this.currentSpec = spec; if (func) { spec.runs(func); } return spec; }; jasmine.Env.prototype.xit = function(desc, func) { return { id: this.nextSpecId(), runs: function() { } }; }; jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) { if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) { return true; } a.__Jasmine_been_here_before__ = b; b.__Jasmine_been_here_before__ = a; var hasKey = function(obj, keyName) { return obj !== null && obj[keyName] !== jasmine.undefined; }; for (var property in b) { if (!hasKey(a, property) && hasKey(b, property)) { mismatchKeys.push("expected has key '" + property + "', but missing from actual."); } } for (property in a) { if (!hasKey(b, property) && hasKey(a, property)) { mismatchKeys.push("expected missing key '" + property + "', but present in actual."); } } for (property in b) { if (property == '__Jasmine_been_here_before__') continue; if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) { mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual."); } } if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) { mismatchValues.push("arrays were not the same length"); } delete a.__Jasmine_been_here_before__; delete b.__Jasmine_been_here_before__; return (mismatchKeys.length === 0 && mismatchValues.length === 0); }; jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) { mismatchKeys = mismatchKeys || []; mismatchValues = mismatchValues || []; for (var i = 0; i < this.equalityTesters_.length; i++) { var equalityTester = this.equalityTesters_[i]; var result = equalityTester(a, b, this, mismatchKeys, mismatchValues); if (result !== jasmine.undefined) return result; } if (a === b) return true; if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) { return (a == jasmine.undefined && b == jasmine.undefined); } if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) { return a === b; } if (a instanceof Date && b instanceof Date) { return a.getTime() == b.getTime(); } if (a instanceof jasmine.Matchers.Any) { return a.matches(b); } if (b instanceof jasmine.Matchers.Any) { return b.matches(a); } if (jasmine.isString_(a) && jasmine.isString_(b)) { return (a == b); } if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) { return (a == b); } if (typeof a === "object" && typeof b === "object") { return this.compareObjects_(a, b, mismatchKeys, mismatchValues); } //Straight check return (a === b); }; jasmine.Env.prototype.contains_ = function(haystack, needle) { if (jasmine.isArray_(haystack)) { for (var i = 0; i < haystack.length; i++) { if (this.equals_(haystack[i], needle)) return true; } return false; } return haystack.indexOf(needle) >= 0; }; jasmine.Env.prototype.addEqualityTester = function(equalityTester) { this.equalityTesters_.push(equalityTester); }; /** No-op base class for Jasmine reporters. * * @constructor */ jasmine.Reporter = function() { }; //noinspection JSUnusedLocalSymbols jasmine.Reporter.prototype.reportRunnerStarting = function(runner) { }; //noinspection JSUnusedLocalSymbols jasmine.Reporter.prototype.reportRunnerResults = function(runner) { }; //noinspection JSUnusedLocalSymbols jasmine.Reporter.prototype.reportSuiteResults = function(suite) { }; //noinspection JSUnusedLocalSymbols jasmine.Reporter.prototype.reportSpecStarting = function(spec) { }; //noinspection JSUnusedLocalSymbols jasmine.Reporter.prototype.reportSpecResults = function(spec) { }; //noinspection JSUnusedLocalSymbols jasmine.Reporter.prototype.log = function(str) { }; /** * Blocks are functions with executable code that make up a spec. * * @constructor * @param {jasmine.Env} env * @param {Function} func * @param {jasmine.Spec} spec */ jasmine.Block = function(env, func, spec) { this.env = env; this.func = func; this.spec = spec; }; jasmine.Block.prototype.execute = function(onComplete) { try { this.func.apply(this.spec); } catch (e) { this.spec.fail(e); } onComplete(); }; /** JavaScript API reporter. * * @constructor */ jasmine.JsApiReporter = function() { this.started = false; this.finished = false; this.suites_ = []; this.results_ = {}; }; jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) { this.started = true; var suites = runner.topLevelSuites(); for (var i = 0; i < suites.length; i++) { var suite = suites[i]; this.suites_.push(this.summarize_(suite)); } }; jasmine.JsApiReporter.prototype.suites = function() { return this.suites_; }; jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) { var isSuite = suiteOrSpec instanceof jasmine.Suite; var summary = { id: suiteOrSpec.id, name: suiteOrSpec.description, type: isSuite ? 'suite' : 'spec', children: [] }; if (isSuite) { var children = suiteOrSpec.children(); for (var i = 0; i < children.length; i++) { summary.children.push(this.summarize_(children[i])); } } return summary; }; jasmine.JsApiReporter.prototype.results = function() { return this.results_; }; jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) { return this.results_[specId]; }; //noinspection JSUnusedLocalSymbols jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) { this.finished = true; }; //noinspection JSUnusedLocalSymbols jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) { }; //noinspection JSUnusedLocalSymbols jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) { this.results_[spec.id] = { messages: spec.results().getItems(), result: spec.results().failedCount > 0 ? "failed" : "passed" }; }; //noinspection JSUnusedLocalSymbols jasmine.JsApiReporter.prototype.log = function(str) { }; jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){ var results = {}; for (var i = 0; i < specIds.length; i++) { var specId = specIds[i]; results[specId] = this.summarizeResult_(this.results_[specId]); } return results; }; jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){ var summaryMessages = []; var messagesLength = result.messages.length; for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) { var resultMessage = result.messages[messageIndex]; summaryMessages.push({ text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined, passed: resultMessage.passed ? resultMessage.passed() : true, type: resultMessage.type, message: resultMessage.message, trace: { stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined } }); } return { result : result.result, messages : summaryMessages }; }; /** * @constructor * @param {jasmine.Env} env * @param actual * @param {jasmine.Spec} spec */ jasmine.Matchers = function(env, actual, spec, opt_isNot) { this.env = env; this.actual = actual; this.spec = spec; this.isNot = opt_isNot || false; this.reportWasCalled_ = false; }; // todo: @deprecated as of Jasmine 0.11, remove soon [xw] jasmine.Matchers.pp = function(str) { throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!"); }; // todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw] jasmine.Matchers.prototype.report = function(result, failing_message, details) { throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs"); }; jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) { for (var methodName in prototype) { if (methodName == 'report') continue; var orig = prototype[methodName]; matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig); } }; jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) { return function() { var matcherArgs = jasmine.util.argsToArray(arguments); var result = matcherFunction.apply(this, arguments); if (this.isNot) { result = !result; } if (this.reportWasCalled_) return result; var message; if (!result) { if (this.message) { message = this.message.apply(this, arguments); if (jasmine.isArray_(message)) { message = message[this.isNot ? 1 : 0]; } } else { var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate; if (matcherArgs.length > 0) { for (var i = 0; i < matcherArgs.length; i++) { if (i > 0) message += ","; message += " " + jasmine.pp(matcherArgs[i]); } } message += "."; } } var expectationResult = new jasmine.ExpectationResult({ matcherName: matcherName, passed: result, expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0], actual: this.actual, message: message }); this.spec.addMatcherResult(expectationResult); return jasmine.undefined; }; }; /** * toBe: compares the actual to the expected using === * @param expected */ jasmine.Matchers.prototype.toBe = function(expected) { return this.actual === expected; }; /** * toNotBe: compares the actual to the expected using !== * @param expected * @deprecated as of 1.0. Use not.toBe() instead. */ jasmine.Matchers.prototype.toNotBe = function(expected) { return this.actual !== expected; }; /** * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc. * * @param expected */ jasmine.Matchers.prototype.toEqual = function(expected) { return this.env.equals_(this.actual, expected); }; /** * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual * @param expected * @deprecated as of 1.0. Use not.toNotEqual() instead. */ jasmine.Matchers.prototype.toNotEqual = function(expected) { return !this.env.equals_(this.actual, expected); }; /** * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes * a pattern or a String. * * @param expected */ jasmine.Matchers.prototype.toMatch = function(expected) { return new RegExp(expected).test(this.actual); }; /** * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch * @param expected * @deprecated as of 1.0. Use not.toMatch() instead. */ jasmine.Matchers.prototype.toNotMatch = function(expected) { return !(new RegExp(expected).test(this.actual)); }; /** * Matcher that compares the actual to jasmine.undefined. */ jasmine.Matchers.prototype.toBeDefined = function() { return (this.actual !== jasmine.undefined); }; /** * Matcher that compares the actual to jasmine.undefined. */ jasmine.Matchers.prototype.toBeUndefined = function() { return (this.actual === jasmine.undefined); }; /** * Matcher that compares the actual to null. */ jasmine.Matchers.prototype.toBeNull = function() { return (this.actual === null); }; /** * Matcher that boolean not-nots the actual. */ jasmine.Matchers.prototype.toBeTruthy = function() { return !!this.actual; }; /** * Matcher that boolean nots the actual. */ jasmine.Matchers.prototype.toBeFalsy = function() { return !this.actual; }; /** * Matcher that checks to see if the actual, a Jasmine spy, was called. */ jasmine.Matchers.prototype.toHaveBeenCalled = function() { if (arguments.length > 0) { throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith'); } if (!jasmine.isSpy(this.actual)) { throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); } this.message = function() { return [ "Expected spy " + this.actual.identity + " to have been called.", "Expected spy " + this.actual.identity + " not to have been called." ]; }; return this.actual.wasCalled; }; /** @deprecated Use expect(xxx).toHaveBeenCalled() instead */ jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled; /** * Matcher that checks to see if the actual, a Jasmine spy, was not called. * * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead */ jasmine.Matchers.prototype.wasNotCalled = function() { if (arguments.length > 0) { throw new Error('wasNotCalled does not take arguments'); } if (!jasmine.isSpy(this.actual)) { throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); } this.message = function() { return [ "Expected spy " + this.actual.identity + " to not have been called.", "Expected spy " + this.actual.identity + " to have been called." ]; }; return !this.actual.wasCalled; }; /** * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters. * * @example * */ jasmine.Matchers.prototype.toHaveBeenCalledWith = function() { var expectedArgs = jasmine.util.argsToArray(arguments); if (!jasmine.isSpy(this.actual)) { throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); } this.message = function() { if (this.actual.callCount === 0) { // todo: what should the failure message for .not.toHaveBeenCalledWith() be? is this right? test better. [xw] return [ "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.", "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but it was." ]; } else { return [ "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall), "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall) ]; } }; return this.env.contains_(this.actual.argsForCall, expectedArgs); }; /** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */ jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith; /** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */ jasmine.Matchers.prototype.wasNotCalledWith = function() { var expectedArgs = jasmine.util.argsToArray(arguments); if (!jasmine.isSpy(this.actual)) { throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); } this.message = function() { return [ "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was", "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was" ]; }; return !this.env.contains_(this.actual.argsForCall, expectedArgs); }; /** * Matcher that checks that the expected item is an element in the actual Array. * * @param {Object} expected */ jasmine.Matchers.prototype.toContain = function(expected) { return this.env.contains_(this.actual, expected); }; /** * Matcher that checks that the expected item is NOT an element in the actual Array. * * @param {Object} expected * @deprecated as of 1.0. Use not.toNotContain() instead. */ jasmine.Matchers.prototype.toNotContain = function(expected) { return !this.env.contains_(this.actual, expected); }; jasmine.Matchers.prototype.toBeLessThan = function(expected) { return this.actual < expected; }; jasmine.Matchers.prototype.toBeGreaterThan = function(expected) { return this.actual > expected; }; /** * Matcher that checks that the expected item is equal to the actual item * up to a given level of decimal precision (default 2). * * @param {Number} expected * @param {Number} precision */ jasmine.Matchers.prototype.toBeCloseTo = function(expected, precision) { if (!(precision === 0)) { precision = precision || 2; } var multiplier = Math.pow(10, precision); var actual = Math.round(this.actual * multiplier); expected = Math.round(expected * multiplier); return expected == actual; }; /** * Matcher that checks that the expected exception was thrown by the actual. * * @param {String} expected */ jasmine.Matchers.prototype.toThrow = function(expected) { var result = false; var exception; if (typeof this.actual != 'function') { throw new Error('Actual is not a function'); } try { this.actual(); } catch (e) { exception = e; } if (exception) { result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected)); } var not = this.isNot ? "not " : ""; this.message = function() { if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) { return ["Expected function " + not + "to throw", expected ? expected.message || expected : "an exception", ", but it threw", exception.message || exception].join(' '); } else { return "Expected function to throw an exception."; } }; return result; }; jasmine.Matchers.Any = function(expectedClass) { this.expectedClass = expectedClass; }; jasmine.Matchers.Any.prototype.matches = function(other) { if (this.expectedClass == String) { return typeof other == 'string' || other instanceof String; } if (this.expectedClass == Number) { return typeof other == 'number' || other instanceof Number; } if (this.expectedClass == Function) { return typeof other == 'function' || other instanceof Function; } if (this.expectedClass == Object) { return typeof other == 'object'; } return other instanceof this.expectedClass; }; jasmine.Matchers.Any.prototype.toString = function() { return ''; }; /** * @constructor */ jasmine.MultiReporter = function() { this.subReporters_ = []; }; jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter); jasmine.MultiReporter.prototype.addReporter = function(reporter) { this.subReporters_.push(reporter); }; (function() { var functionNames = [ "reportRunnerStarting", "reportRunnerResults", "reportSuiteResults", "reportSpecStarting", "reportSpecResults", "log" ]; for (var i = 0; i < functionNames.length; i++) { var functionName = functionNames[i]; jasmine.MultiReporter.prototype[functionName] = (function(functionName) { return function() { for (var j = 0; j < this.subReporters_.length; j++) { var subReporter = this.subReporters_[j]; if (subReporter[functionName]) { subReporter[functionName].apply(subReporter, arguments); } } }; })(functionName); } })(); /** * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults * * @constructor */ jasmine.NestedResults = function() { /** * The total count of results */ this.totalCount = 0; /** * Number of passed results */ this.passedCount = 0; /** * Number of failed results */ this.failedCount = 0; /** * Was this suite/spec skipped? */ this.skipped = false; /** * @ignore */ this.items_ = []; }; /** * Roll up the result counts. * * @param result */ jasmine.NestedResults.prototype.rollupCounts = function(result) { this.totalCount += result.totalCount; this.passedCount += result.passedCount; this.failedCount += result.failedCount; }; /** * Adds a log message. * @param values Array of message parts which will be concatenated later. */ jasmine.NestedResults.prototype.log = function(values) { this.items_.push(new jasmine.MessageResult(values)); }; /** * Getter for the results: message & results. */ jasmine.NestedResults.prototype.getItems = function() { return this.items_; }; /** * Adds a result, tracking counts (total, passed, & failed) * @param {jasmine.ExpectationResult|jasmine.NestedResults} result */ jasmine.NestedResults.prototype.addResult = function(result) { if (result.type != 'log') { if (result.items_) { this.rollupCounts(result); } else { this.totalCount++; if (result.passed()) { this.passedCount++; } else { this.failedCount++; } } } this.items_.push(result); }; /** * @returns {Boolean} True if everything below passed */ jasmine.NestedResults.prototype.passed = function() { return this.passedCount === this.totalCount; }; /** * Base class for pretty printing for expectation results. */ jasmine.PrettyPrinter = function() { this.ppNestLevel_ = 0; }; /** * Formats a value in a nice, human-readable string. * * @param value */ jasmine.PrettyPrinter.prototype.format = function(value) { if (this.ppNestLevel_ > 40) { throw new Error('jasmine.PrettyPrinter: format() nested too deeply!'); } this.ppNestLevel_++; try { if (value === jasmine.undefined) { this.emitScalar('undefined'); } else if (value === null) { this.emitScalar('null'); } else if (value === jasmine.getGlobal()) { this.emitScalar(''); } else if (value instanceof jasmine.Matchers.Any) { this.emitScalar(value.toString()); } else if (typeof value === 'string') { this.emitString(value); } else if (jasmine.isSpy(value)) { this.emitScalar("spy on " + value.identity); } else if (value instanceof RegExp) { this.emitScalar(value.toString()); } else if (typeof value === 'function') { this.emitScalar('Function'); } else if (typeof value.nodeType === 'number') { this.emitScalar('HTMLNode'); } else if (value instanceof Date) { this.emitScalar('Date(' + value + ')'); } else if (value.__Jasmine_been_here_before__) { this.emitScalar(''); } else if (jasmine.isArray_(value) || typeof value == 'object') { value.__Jasmine_been_here_before__ = true; if (jasmine.isArray_(value)) { this.emitArray(value); } else { this.emitObject(value); } delete value.__Jasmine_been_here_before__; } else { this.emitScalar(value.toString()); } } finally { this.ppNestLevel_--; } }; jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) { for (var property in obj) { if (property == '__Jasmine_been_here_before__') continue; fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) !== jasmine.undefined && obj.__lookupGetter__(property) !== null) : false); } }; jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_; jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_; jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_; jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_; jasmine.StringPrettyPrinter = function() { jasmine.PrettyPrinter.call(this); this.string = ''; }; jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter); jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) { this.append(value); }; jasmine.StringPrettyPrinter.prototype.emitString = function(value) { this.append("'" + value + "'"); }; jasmine.StringPrettyPrinter.prototype.emitArray = function(array) { this.append('[ '); for (var i = 0; i < array.length; i++) { if (i > 0) { this.append(', '); } this.format(array[i]); } this.append(' ]'); }; jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) { var self = this; this.append('{ '); var first = true; this.iterateObject(obj, function(property, isGetter) { if (first) { first = false; } else { self.append(', '); } self.append(property); self.append(' : '); if (isGetter) { self.append(''); } else { self.format(obj[property]); } }); this.append(' }'); }; jasmine.StringPrettyPrinter.prototype.append = function(value) { this.string += value; }; jasmine.Queue = function(env) { this.env = env; this.blocks = []; this.running = false; this.index = 0; this.offset = 0; this.abort = false; }; jasmine.Queue.prototype.addBefore = function(block) { this.blocks.unshift(block); }; jasmine.Queue.prototype.add = function(block) { this.blocks.push(block); }; jasmine.Queue.prototype.insertNext = function(block) { this.blocks.splice((this.index + this.offset + 1), 0, block); this.offset++; }; jasmine.Queue.prototype.start = function(onComplete) { this.running = true; this.onComplete = onComplete; this.next_(); }; jasmine.Queue.prototype.isRunning = function() { return this.running; }; jasmine.Queue.LOOP_DONT_RECURSE = true; jasmine.Queue.prototype.next_ = function() { var self = this; var goAgain = true; while (goAgain) { goAgain = false; if (self.index < self.blocks.length && !this.abort) { var calledSynchronously = true; var completedSynchronously = false; var onComplete = function () { if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) { completedSynchronously = true; return; } if (self.blocks[self.index].abort) { self.abort = true; } self.offset = 0; self.index++; var now = new Date().getTime(); if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) { self.env.lastUpdate = now; self.env.setTimeout(function() { self.next_(); }, 0); } else { if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) { goAgain = true; } else { self.next_(); } } }; self.blocks[self.index].execute(onComplete); calledSynchronously = false; if (completedSynchronously) { onComplete(); } } else { self.running = false; if (self.onComplete) { self.onComplete(); } } } }; jasmine.Queue.prototype.results = function() { var results = new jasmine.NestedResults(); for (var i = 0; i < this.blocks.length; i++) { if (this.blocks[i].results) { results.addResult(this.blocks[i].results()); } } return results; }; /** * Runner * * @constructor * @param {jasmine.Env} env */ jasmine.Runner = function(env) { var self = this; self.env = env; self.queue = new jasmine.Queue(env); self.before_ = []; self.after_ = []; self.suites_ = []; }; jasmine.Runner.prototype.execute = function() { var self = this; if (self.env.reporter.reportRunnerStarting) { self.env.reporter.reportRunnerStarting(this); } self.queue.start(function () { self.finishCallback(); }); }; jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) { beforeEachFunction.typeName = 'beforeEach'; this.before_.splice(0,0,beforeEachFunction); }; jasmine.Runner.prototype.afterEach = function(afterEachFunction) { afterEachFunction.typeName = 'afterEach'; this.after_.splice(0,0,afterEachFunction); }; jasmine.Runner.prototype.finishCallback = function() { this.env.reporter.reportRunnerResults(this); }; jasmine.Runner.prototype.addSuite = function(suite) { this.suites_.push(suite); }; jasmine.Runner.prototype.add = function(block) { if (block instanceof jasmine.Suite) { this.addSuite(block); } this.queue.add(block); }; jasmine.Runner.prototype.specs = function () { var suites = this.suites(); var specs = []; for (var i = 0; i < suites.length; i++) { specs = specs.concat(suites[i].specs()); } return specs; }; jasmine.Runner.prototype.suites = function() { return this.suites_; }; jasmine.Runner.prototype.topLevelSuites = function() { var topLevelSuites = []; for (var i = 0; i < this.suites_.length; i++) { if (!this.suites_[i].parentSuite) { topLevelSuites.push(this.suites_[i]); } } return topLevelSuites; }; jasmine.Runner.prototype.results = function() { return this.queue.results(); }; /** * Internal representation of a Jasmine specification, or test. * * @constructor * @param {jasmine.Env} env * @param {jasmine.Suite} suite * @param {String} description */ jasmine.Spec = function(env, suite, description) { if (!env) { throw new Error('jasmine.Env() required'); } if (!suite) { throw new Error('jasmine.Suite() required'); } var spec = this; spec.id = env.nextSpecId ? env.nextSpecId() : null; spec.env = env; spec.suite = suite; spec.description = description; spec.queue = new jasmine.Queue(env); spec.afterCallbacks = []; spec.spies_ = []; spec.results_ = new jasmine.NestedResults(); spec.results_.description = description; spec.matchersClass = null; }; jasmine.Spec.prototype.getFullName = function() { return this.suite.getFullName() + ' ' + this.description + '.'; }; jasmine.Spec.prototype.results = function() { return this.results_; }; /** * All parameters are pretty-printed and concatenated together, then written to the spec's output. * * Be careful not to leave calls to jasmine.log in production code. */ jasmine.Spec.prototype.log = function() { return this.results_.log(arguments); }; jasmine.Spec.prototype.runs = function (func) { var block = new jasmine.Block(this.env, func, this); this.addToQueue(block); return this; }; jasmine.Spec.prototype.addToQueue = function (block) { if (this.queue.isRunning()) { this.queue.insertNext(block); } else { this.queue.add(block); } }; /** * @param {jasmine.ExpectationResult} result */ jasmine.Spec.prototype.addMatcherResult = function(result) { this.results_.addResult(result); }; jasmine.Spec.prototype.expect = function(actual) { var positive = new (this.getMatchersClass_())(this.env, actual, this); positive.not = new (this.getMatchersClass_())(this.env, actual, this, true); return positive; }; /** * Waits a fixed time period before moving to the next block. * * @deprecated Use waitsFor() instead * @param {Number} timeout milliseconds to wait */ jasmine.Spec.prototype.waits = function(timeout) { var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this); this.addToQueue(waitsFunc); return this; }; /** * Waits for the latchFunction to return true before proceeding to the next block. * * @param {Function} latchFunction * @param {String} optional_timeoutMessage * @param {Number} optional_timeout */ jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { var latchFunction_ = null; var optional_timeoutMessage_ = null; var optional_timeout_ = null; for (var i = 0; i < arguments.length; i++) { var arg = arguments[i]; switch (typeof arg) { case 'function': latchFunction_ = arg; break; case 'string': optional_timeoutMessage_ = arg; break; case 'number': optional_timeout_ = arg; break; } } var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this); this.addToQueue(waitsForFunc); return this; }; jasmine.Spec.prototype.fail = function (e) { var expectationResult = new jasmine.ExpectationResult({ passed: false, message: e ? jasmine.util.formatException(e) : 'Exception', trace: { stack: e.stack } }); this.results_.addResult(expectationResult); }; jasmine.Spec.prototype.getMatchersClass_ = function() { return this.matchersClass || this.env.matchersClass; }; jasmine.Spec.prototype.addMatchers = function(matchersPrototype) { var parent = this.getMatchersClass_(); var newMatchersClass = function() { parent.apply(this, arguments); }; jasmine.util.inherit(newMatchersClass, parent); jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass); this.matchersClass = newMatchersClass; }; jasmine.Spec.prototype.finishCallback = function() { this.env.reporter.reportSpecResults(this); }; jasmine.Spec.prototype.finish = function(onComplete) { this.removeAllSpies(); this.finishCallback(); if (onComplete) { onComplete(); } }; jasmine.Spec.prototype.after = function(doAfter) { if (this.queue.isRunning()) { this.queue.add(new jasmine.Block(this.env, doAfter, this)); } else { this.afterCallbacks.unshift(doAfter); } }; jasmine.Spec.prototype.execute = function(onComplete) { var spec = this; if (!spec.env.specFilter(spec)) { spec.results_.skipped = true; spec.finish(onComplete); return; } this.env.reporter.reportSpecStarting(this); spec.env.currentSpec = spec; spec.addBeforesAndAftersToQueue(); spec.queue.start(function () { spec.finish(onComplete); }); }; jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() { var runner = this.env.currentRunner(); var i; for (var suite = this.suite; suite; suite = suite.parentSuite) { for (i = 0; i < suite.before_.length; i++) { this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this)); } } for (i = 0; i < runner.before_.length; i++) { this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this)); } for (i = 0; i < this.afterCallbacks.length; i++) { this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this)); } for (suite = this.suite; suite; suite = suite.parentSuite) { for (i = 0; i < suite.after_.length; i++) { this.queue.add(new jasmine.Block(this.env, suite.after_[i], this)); } } for (i = 0; i < runner.after_.length; i++) { this.queue.add(new jasmine.Block(this.env, runner.after_[i], this)); } }; jasmine.Spec.prototype.explodes = function() { throw 'explodes function should not have been called'; }; jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) { if (obj == jasmine.undefined) { throw "spyOn could not find an object to spy upon for " + methodName + "()"; } if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) { throw methodName + '() method does not exist'; } if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) { throw new Error(methodName + ' has already been spied upon'); } var spyObj = jasmine.createSpy(methodName); this.spies_.push(spyObj); spyObj.baseObj = obj; spyObj.methodName = methodName; spyObj.originalValue = obj[methodName]; obj[methodName] = spyObj; return spyObj; }; jasmine.Spec.prototype.removeAllSpies = function() { for (var i = 0; i < this.spies_.length; i++) { var spy = this.spies_[i]; spy.baseObj[spy.methodName] = spy.originalValue; } this.spies_ = []; }; /** * Internal representation of a Jasmine suite. * * @constructor * @param {jasmine.Env} env * @param {String} description * @param {Function} specDefinitions * @param {jasmine.Suite} parentSuite */ jasmine.Suite = function(env, description, specDefinitions, parentSuite) { var self = this; self.id = env.nextSuiteId ? env.nextSuiteId() : null; self.description = description; self.queue = new jasmine.Queue(env); self.parentSuite = parentSuite; self.env = env; self.before_ = []; self.after_ = []; self.children_ = []; self.suites_ = []; self.specs_ = []; }; jasmine.Suite.prototype.getFullName = function() { var fullName = this.description; for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { fullName = parentSuite.description + ' ' + fullName; } return fullName; }; jasmine.Suite.prototype.finish = function(onComplete) { this.env.reporter.reportSuiteResults(this); this.finished = true; if (typeof(onComplete) == 'function') { onComplete(); } }; jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) { beforeEachFunction.typeName = 'beforeEach'; this.before_.unshift(beforeEachFunction); }; jasmine.Suite.prototype.afterEach = function(afterEachFunction) { afterEachFunction.typeName = 'afterEach'; this.after_.unshift(afterEachFunction); }; jasmine.Suite.prototype.results = function() { return this.queue.results(); }; jasmine.Suite.prototype.add = function(suiteOrSpec) { this.children_.push(suiteOrSpec); if (suiteOrSpec instanceof jasmine.Suite) { this.suites_.push(suiteOrSpec); this.env.currentRunner().addSuite(suiteOrSpec); } else { this.specs_.push(suiteOrSpec); } this.queue.add(suiteOrSpec); }; jasmine.Suite.prototype.specs = function() { return this.specs_; }; jasmine.Suite.prototype.suites = function() { return this.suites_; }; jasmine.Suite.prototype.children = function() { return this.children_; }; jasmine.Suite.prototype.execute = function(onComplete) { var self = this; this.queue.start(function () { self.finish(onComplete); }); }; jasmine.WaitsBlock = function(env, timeout, spec) { this.timeout = timeout; jasmine.Block.call(this, env, null, spec); }; jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block); jasmine.WaitsBlock.prototype.execute = function (onComplete) { if (jasmine.VERBOSE) { this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...'); } this.env.setTimeout(function () { onComplete(); }, this.timeout); }; /** * A block which waits for some condition to become true, with timeout. * * @constructor * @extends jasmine.Block * @param {jasmine.Env} env The Jasmine environment. * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true. * @param {Function} latchFunction A function which returns true when the desired condition has been met. * @param {String} message The message to display if the desired condition hasn't been met within the given time period. * @param {jasmine.Spec} spec The Jasmine spec. */ jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) { this.timeout = timeout || env.defaultTimeoutInterval; this.latchFunction = latchFunction; this.message = message; this.totalTimeSpentWaitingForLatch = 0; jasmine.Block.call(this, env, null, spec); }; jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block); jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10; jasmine.WaitsForBlock.prototype.execute = function(onComplete) { if (jasmine.VERBOSE) { this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen')); } var latchFunctionResult; try { latchFunctionResult = this.latchFunction.apply(this.spec); } catch (e) { this.spec.fail(e); onComplete(); return; } if (latchFunctionResult) { onComplete(); } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) { var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen'); this.spec.fail({ name: 'timeout', message: message }); this.abort = true; onComplete(); } else { this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT; var self = this; this.env.setTimeout(function() { self.execute(onComplete); }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT); } }; // Mock setTimeout, clearTimeout // Contributed by Pivotal Computer Systems, www.pivotalsf.com jasmine.FakeTimer = function() { this.reset(); var self = this; self.setTimeout = function(funcToCall, millis) { self.timeoutsMade++; self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false); return self.timeoutsMade; }; self.setInterval = function(funcToCall, millis) { self.timeoutsMade++; self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true); return self.timeoutsMade; }; self.clearTimeout = function(timeoutKey) { self.scheduledFunctions[timeoutKey] = jasmine.undefined; }; self.clearInterval = function(timeoutKey) { self.scheduledFunctions[timeoutKey] = jasmine.undefined; }; }; jasmine.FakeTimer.prototype.reset = function() { this.timeoutsMade = 0; this.scheduledFunctions = {}; this.nowMillis = 0; }; jasmine.FakeTimer.prototype.tick = function(millis) { var oldMillis = this.nowMillis; var newMillis = oldMillis + millis; this.runFunctionsWithinRange(oldMillis, newMillis); this.nowMillis = newMillis; }; jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) { var scheduledFunc; var funcsToRun = []; for (var timeoutKey in this.scheduledFunctions) { scheduledFunc = this.scheduledFunctions[timeoutKey]; if (scheduledFunc != jasmine.undefined && scheduledFunc.runAtMillis >= oldMillis && scheduledFunc.runAtMillis <= nowMillis) { funcsToRun.push(scheduledFunc); this.scheduledFunctions[timeoutKey] = jasmine.undefined; } } if (funcsToRun.length > 0) { funcsToRun.sort(function(a, b) { return a.runAtMillis - b.runAtMillis; }); for (var i = 0; i < funcsToRun.length; ++i) { try { var funcToRun = funcsToRun[i]; this.nowMillis = funcToRun.runAtMillis; funcToRun.funcToCall(); if (funcToRun.recurring) { this.scheduleFunction(funcToRun.timeoutKey, funcToRun.funcToCall, funcToRun.millis, true); } } catch(e) { } } this.runFunctionsWithinRange(oldMillis, nowMillis); } }; jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) { this.scheduledFunctions[timeoutKey] = { runAtMillis: this.nowMillis + millis, funcToCall: funcToCall, recurring: recurring, timeoutKey: timeoutKey, millis: millis }; }; /** * @namespace */ jasmine.Clock = { defaultFakeTimer: new jasmine.FakeTimer(), reset: function() { jasmine.Clock.assertInstalled(); jasmine.Clock.defaultFakeTimer.reset(); }, tick: function(millis) { jasmine.Clock.assertInstalled(); jasmine.Clock.defaultFakeTimer.tick(millis); }, runFunctionsWithinRange: function(oldMillis, nowMillis) { jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis); }, scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) { jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring); }, useMock: function() { if (!jasmine.Clock.isInstalled()) { var spec = jasmine.getEnv().currentSpec; spec.after(jasmine.Clock.uninstallMock); jasmine.Clock.installMock(); } }, installMock: function() { jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer; }, uninstallMock: function() { jasmine.Clock.assertInstalled(); jasmine.Clock.installed = jasmine.Clock.real; }, real: { setTimeout: jasmine.getGlobal().setTimeout, clearTimeout: jasmine.getGlobal().clearTimeout, setInterval: jasmine.getGlobal().setInterval, clearInterval: jasmine.getGlobal().clearInterval }, assertInstalled: function() { if (!jasmine.Clock.isInstalled()) { throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()"); } }, isInstalled: function() { return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer; }, installed: null }; jasmine.Clock.installed = jasmine.Clock.real; //else for IE support jasmine.getGlobal().setTimeout = function(funcToCall, millis) { if (jasmine.Clock.installed.setTimeout.apply) { return jasmine.Clock.installed.setTimeout.apply(this, arguments); } else { return jasmine.Clock.installed.setTimeout(funcToCall, millis); } }; jasmine.getGlobal().setInterval = function(funcToCall, millis) { if (jasmine.Clock.installed.setInterval.apply) { return jasmine.Clock.installed.setInterval.apply(this, arguments); } else { return jasmine.Clock.installed.setInterval(funcToCall, millis); } }; jasmine.getGlobal().clearTimeout = function(timeoutKey) { if (jasmine.Clock.installed.clearTimeout.apply) { return jasmine.Clock.installed.clearTimeout.apply(this, arguments); } else { return jasmine.Clock.installed.clearTimeout(timeoutKey); } }; jasmine.getGlobal().clearInterval = function(timeoutKey) { if (jasmine.Clock.installed.clearTimeout.apply) { return jasmine.Clock.installed.clearInterval.apply(this, arguments); } else { return jasmine.Clock.installed.clearInterval(timeoutKey); } }; jasmine.version_= { "major": 1, "minor": 1, "build": 0, "revision": 1308187385, "rc": 1 } modestmaps-js-3.3.6/test/lib/jasmine-1.1.0.rc1/jasmine_favicon.png000066400000000000000000000016111210227260500244400ustar00rootroot00000000000000PNG  IHDRagAMA asRGB cHRMz&u0`:pQ<bKGD pHYs  IDAT8˥KHTaOs8KT񒗌2"ZA-EЦMѦ-" EQ= -2Gr2oQR|7_LNO_fw{[Zld 2lŴ뉱+OjƓέ|k>;Fnδ"mz?4E]`_sk:ҜN#$a͆u+ggeߺz;]fDQjG58c(Θ6[,P4ɳ:d޹_/B@1347x?HT0'mEv"@)5ZM`?<~8ԊˋJɒdaTG$ыCXd :ܼYK.ir #j2)jU⚵n),+7Sc $9=bs z"`ĒU^:*)+XkϮs˳H  :]B2S5eL(eb65}}o<]r1ElɐR`gg#74xߵx[m$4s,Wc]0c$AVU%쫯oM4# `ρR1}p/3Nd-wGcJKWW/Hĝ7?g)&6<IENDB`modestmaps-js-3.3.6/test/lib/jasmine-1.2.0.rc3/000077500000000000000000000000001210227260500206035ustar00rootroot00000000000000modestmaps-js-3.3.6/test/lib/jasmine-1.2.0.rc3/MIT.LICENSE000066400000000000000000000020451210227260500222410ustar00rootroot00000000000000Copyright (c) 2008-2011 Pivotal Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. modestmaps-js-3.3.6/test/lib/jasmine-1.2.0.rc3/jasmine-html.js000066400000000000000000000451711210227260500235410ustar00rootroot00000000000000jasmine.HtmlReporterHelpers = {}; jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) { var el = document.createElement(type); for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child === 'string') { el.appendChild(document.createTextNode(child)); } else { if (child) { el.appendChild(child); } } } for (var attr in attrs) { if (attr == "className") { el[attr] = attrs[attr]; } else { el.setAttribute(attr, attrs[attr]); } } return el; }; jasmine.HtmlReporterHelpers.getSpecStatus = function(child) { var results = child.results(); var status = results.passed() ? 'passed' : 'failed'; if (results.skipped) { status = 'skipped'; } return status; }; jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) { var parentDiv = this.dom.summary; var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite'; var parent = child[parentSuite]; if (parent) { if (typeof this.views.suites[parent.id] == 'undefined') { this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views); } parentDiv = this.views.suites[parent.id].element; } parentDiv.appendChild(childElement); }; jasmine.HtmlReporterHelpers.addHelpers = function(ctor) { for(var fn in jasmine.HtmlReporterHelpers) { ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn]; } }; jasmine.HtmlReporter = function(_doc) { var self = this; var doc = _doc || window.document; var reporterView; var dom = {}; // Jasmine Reporter Public Interface self.logRunningSpecs = false; self.reportRunnerStarting = function(runner) { var specs = runner.specs() || []; if (specs.length == 0) { return; } createReporterDom(runner.env.versionString()); doc.body.appendChild(dom.reporter); reporterView = new jasmine.HtmlReporter.ReporterView(dom); reporterView.addSpecs(specs, self.specFilter); }; self.reportRunnerResults = function(runner) { reporterView && reporterView.complete(); }; self.reportSuiteResults = function(suite) { reporterView.suiteComplete(suite); }; self.reportSpecStarting = function(spec) { if (self.logRunningSpecs) { self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); } }; self.reportSpecResults = function(spec) { reporterView.specComplete(spec); }; self.log = function() { var console = jasmine.getGlobal().console; if (console && console.log) { if (console.log.apply) { console.log.apply(console, arguments); } else { console.log(arguments); // ie fix: console.log.apply doesn't exist on ie } } }; self.specFilter = function(spec) { if (!focusedSpecName()) { return true; } return spec.getFullName().indexOf(focusedSpecName()) === 0; }; return self; function focusedSpecName() { var specName; (function memoizeFocusedSpec() { if (specName) { return; } var paramMap = []; var params = doc.location.search.substring(1).split('&'); for (var i = 0; i < params.length; i++) { var p = params[i].split('='); paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); } specName = paramMap.spec; })(); return specName; } function createReporterDom(version) { dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' }, dom.banner = self.createDom('div', { className: 'banner' }, self.createDom('span', { className: 'title' }, "Jasmine "), self.createDom('span', { className: 'version' }, version)), dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}), dom.alert = self.createDom('div', {className: 'alert'}), dom.results = self.createDom('div', {className: 'results'}, dom.summary = self.createDom('div', { className: 'summary' }), dom.details = self.createDom('div', { id: 'details' })) ); } }; jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter);jasmine.HtmlReporter.ReporterView = function(dom) { this.startedAt = new Date(); this.runningSpecCount = 0; this.completeSpecCount = 0; this.passedCount = 0; this.failedCount = 0; this.skippedCount = 0; this.createResultsMenu = function() { this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'}, this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'), ' | ', this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing')); this.summaryMenuItem.onclick = function() { dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, ''); }; this.detailsMenuItem.onclick = function() { showDetails(); }; }; this.addSpecs = function(specs, specFilter) { this.totalSpecCount = specs.length; this.views = { specs: {}, suites: {} }; for (var i = 0; i < specs.length; i++) { var spec = specs[i]; this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views); if (specFilter(spec)) { this.runningSpecCount++; } } }; this.specComplete = function(spec) { this.completeSpecCount++; if (isUndefined(this.views.specs[spec.id])) { this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom); } var specView = this.views.specs[spec.id]; switch (specView.status()) { case 'passed': this.passedCount++; break; case 'failed': this.failedCount++; break; case 'skipped': this.skippedCount++; break; } specView.refresh(); this.refresh(); }; this.suiteComplete = function(suite) { var suiteView = this.views.suites[suite.id]; if (isUndefined(suiteView)) { return; } suiteView.refresh(); }; this.refresh = function() { if (isUndefined(this.resultsMenu)) { this.createResultsMenu(); } // currently running UI if (isUndefined(this.runningAlert)) { this.runningAlert = this.createDom('a', {href: "?", className: "runningAlert bar"}); dom.alert.appendChild(this.runningAlert); } this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount); // skipped specs UI if (isUndefined(this.skippedAlert)) { this.skippedAlert = this.createDom('a', {href: "?", className: "skippedAlert bar"}); } this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all"; if (this.skippedCount === 1 && isDefined(dom.alert)) { dom.alert.appendChild(this.skippedAlert); } // passing specs UI if (isUndefined(this.passedAlert)) { this.passedAlert = this.createDom('span', {href: "?", className: "passingAlert bar"}); } this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount); // failing specs UI if (isUndefined(this.failedAlert)) { this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"}); } this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount); if (this.failedCount === 1 && isDefined(dom.alert)) { dom.alert.appendChild(this.failedAlert); dom.alert.appendChild(this.resultsMenu); } // summary info this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount); this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing"; }; this.complete = function() { dom.alert.removeChild(this.runningAlert); this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all"; if (this.failedCount === 0) { dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount))); } else { showDetails(); } dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s")); }; return this; function showDetails() { if (dom.reporter.className.search(/showDetails/) === -1) { dom.reporter.className += " showDetails"; } } function isUndefined(obj) { return typeof obj === 'undefined'; } function isDefined(obj) { return !isUndefined(obj); } function specPluralizedFor(count) { var str = count + " spec"; if (count > 1) { str += "s" } return str; } }; jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView); jasmine.HtmlReporter.SpecView = function(spec, dom, views) { this.spec = spec; this.dom = dom; this.views = views; this.symbol = this.createDom('li', { className: 'pending' }); this.dom.symbolSummary.appendChild(this.symbol); this.summary = this.createDom('div', { className: 'specSummary' }, this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(this.spec.getFullName()), title: this.spec.getFullName() }, this.spec.description) ); this.detail = this.createDom('div', { className: 'specDetail' }, this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(this.spec.getFullName()), title: this.spec.getFullName() }, this.spec.getFullName()) ); }; jasmine.HtmlReporter.SpecView.prototype.status = function() { return this.getSpecStatus(this.spec); }; jasmine.HtmlReporter.SpecView.prototype.refresh = function() { this.symbol.className = this.status(); switch (this.status()) { case 'skipped': break; case 'passed': this.appendSummaryToSuiteDiv(); break; case 'failed': this.appendSummaryToSuiteDiv(); this.appendFailureDetail(); break; } }; jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() { this.summary.className += ' ' + this.status(); this.appendToSummary(this.spec, this.summary); }; jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() { this.detail.className += ' ' + this.status(); var resultItems = this.spec.results().getItems(); var messagesDiv = this.createDom('div', { className: 'messages' }); for (var i = 0; i < resultItems.length; i++) { var result = resultItems[i]; if (result.type == 'log') { messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); } else if (result.type == 'expect' && result.passed && !result.passed()) { messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); if (result.trace.stack) { messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); } } } if (messagesDiv.childNodes.length > 0) { this.detail.appendChild(messagesDiv); this.dom.details.appendChild(this.detail); } }; jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.HtmlReporter.SuiteView = function(suite, dom, views) { this.suite = suite; this.dom = dom; this.views = views; this.element = this.createDom('div', { className: 'suite' }, this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(this.suite.getFullName()) }, this.suite.description) ); this.appendToSummary(this.suite, this.element); }; jasmine.HtmlReporter.SuiteView.prototype.status = function() { return this.getSpecStatus(this.suite); }; jasmine.HtmlReporter.SuiteView.prototype.refresh = function() { this.element.className += " " + this.status(); }; jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView); /* @deprecated Use jasmine.HtmlReporter instead */ jasmine.TrivialReporter = function(doc) { this.document = doc || document; this.suiteDivs = {}; this.logRunningSpecs = false; }; jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) { var el = document.createElement(type); for (var i = 2; i < arguments.length; i++) { var child = arguments[i]; if (typeof child === 'string') { el.appendChild(document.createTextNode(child)); } else { if (child) { el.appendChild(child); } } } for (var attr in attrs) { if (attr == "className") { el[attr] = attrs[attr]; } else { el.setAttribute(attr, attrs[attr]); } } return el; }; jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) { var showPassed, showSkipped; this.outerDiv = this.createDom('div', { id: 'TrivialReporter', className: 'jasmine_reporter' }, this.createDom('div', { className: 'banner' }, this.createDom('div', { className: 'logo' }, this.createDom('span', { className: 'title' }, "Jasmine"), this.createDom('span', { className: 'version' }, runner.env.versionString())), this.createDom('div', { className: 'options' }, "Show ", showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }), this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "), showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }), this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped") ) ), this.runnerDiv = this.createDom('div', { className: 'runner running' }, this.createDom('a', { className: 'run_spec', href: '?' }, "run all"), this.runnerMessageSpan = this.createDom('span', {}, "Running..."), this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, "")) ); this.document.body.appendChild(this.outerDiv); var suites = runner.suites(); for (var i = 0; i < suites.length; i++) { var suite = suites[i]; var suiteDiv = this.createDom('div', { className: 'suite' }, this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"), this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description)); this.suiteDivs[suite.id] = suiteDiv; var parentDiv = this.outerDiv; if (suite.parentSuite) { parentDiv = this.suiteDivs[suite.parentSuite.id]; } parentDiv.appendChild(suiteDiv); } this.startedAt = new Date(); var self = this; showPassed.onclick = function(evt) { if (showPassed.checked) { self.outerDiv.className += ' show-passed'; } else { self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, ''); } }; showSkipped.onclick = function(evt) { if (showSkipped.checked) { self.outerDiv.className += ' show-skipped'; } else { self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, ''); } }; }; jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) { var results = runner.results(); var className = (results.failedCount > 0) ? "runner failed" : "runner passed"; this.runnerDiv.setAttribute("class", className); //do it twice for IE this.runnerDiv.setAttribute("className", className); var specs = runner.specs(); var specCount = 0; for (var i = 0; i < specs.length; i++) { if (this.specFilter(specs[i])) { specCount++; } } var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s"); message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"; this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild); this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString())); }; jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) { var results = suite.results(); var status = results.passed() ? 'passed' : 'failed'; if (results.totalCount === 0) { // todo: change this to check results.skipped status = 'skipped'; } this.suiteDivs[suite.id].className += " " + status; }; jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) { if (this.logRunningSpecs) { this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); } }; jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) { var results = spec.results(); var status = results.passed() ? 'passed' : 'failed'; if (results.skipped) { status = 'skipped'; } var specDiv = this.createDom('div', { className: 'spec ' + status }, this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"), this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(spec.getFullName()), title: spec.getFullName() }, spec.description)); var resultItems = results.getItems(); var messagesDiv = this.createDom('div', { className: 'messages' }); for (var i = 0; i < resultItems.length; i++) { var result = resultItems[i]; if (result.type == 'log') { messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); } else if (result.type == 'expect' && result.passed && !result.passed()) { messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); if (result.trace.stack) { messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); } } } if (messagesDiv.childNodes.length > 0) { specDiv.appendChild(messagesDiv); } this.suiteDivs[spec.suite.id].appendChild(specDiv); }; jasmine.TrivialReporter.prototype.log = function() { var console = jasmine.getGlobal().console; if (console && console.log) { if (console.log.apply) { console.log.apply(console, arguments); } else { console.log(arguments); // ie fix: console.log.apply doesn't exist on ie } } }; jasmine.TrivialReporter.prototype.getLocation = function() { return this.document.location; }; jasmine.TrivialReporter.prototype.specFilter = function(spec) { var paramMap = {}; var params = this.getLocation().search.substring(1).split('&'); for (var i = 0; i < params.length; i++) { var p = params[i].split('='); paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); } if (!paramMap.spec) { return true; } return spec.getFullName().indexOf(paramMap.spec) === 0; }; modestmaps-js-3.3.6/test/lib/jasmine-1.2.0.rc3/jasmine.css000066400000000000000000000144541210227260500227530ustar00rootroot00000000000000body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; } #HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; } #HTMLReporter a { text-decoration: none; } #HTMLReporter a:hover { text-decoration: underline; } #HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; } #HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; } #HTMLReporter #jasmine_content { position: fixed; right: 100%; } #HTMLReporter .version { color: #aaaaaa; } #HTMLReporter .banner { margin-top: 14px; } #HTMLReporter .duration { color: #aaaaaa; float: right; } #HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; } #HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; } #HTMLReporter .symbolSummary li.passed { font-size: 14px; } #HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; } #HTMLReporter .symbolSummary li.failed { line-height: 9px; } #HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; } #HTMLReporter .symbolSummary li.skipped { font-size: 14px; } #HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; } #HTMLReporter .symbolSummary li.pending { line-height: 11px; } #HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; } #HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; } #HTMLReporter .runningAlert { background-color: #666666; } #HTMLReporter .skippedAlert { background-color: #aaaaaa; } #HTMLReporter .skippedAlert:first-child { background-color: #333333; } #HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; } #HTMLReporter .passingAlert { background-color: #a6b779; } #HTMLReporter .passingAlert:first-child { background-color: #5e7d00; } #HTMLReporter .failingAlert { background-color: #cf867e; } #HTMLReporter .failingAlert:first-child { background-color: #b03911; } #HTMLReporter .results { margin-top: 14px; } #HTMLReporter #details { display: none; } #HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; } #HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; } #HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; } #HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; } #HTMLReporter.showDetails .summary { display: none; } #HTMLReporter.showDetails #details { display: block; } #HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; } #HTMLReporter .summary { margin-top: 14px; } #HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; } #HTMLReporter .summary .specSummary.passed a { color: #5e7d00; } #HTMLReporter .summary .specSummary.failed a { color: #b03911; } #HTMLReporter .description + .suite { margin-top: 0; } #HTMLReporter .suite { margin-top: 14px; } #HTMLReporter .suite a { color: #333333; } #HTMLReporter #details .specDetail { margin-bottom: 28px; } #HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; } #HTMLReporter .resultMessage { padding-top: 14px; color: #333333; } #HTMLReporter .resultMessage span.result { display: block; } #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; } #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;*/ /*}*/ } #TrivialReporter a:visited, #TrivialReporter a { color: #303; } #TrivialReporter a:hover, #TrivialReporter a:active { color: blue; } #TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; } #TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; } #TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; } #TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; } #TrivialReporter .runner.running { background-color: yellow; } #TrivialReporter .options { text-align: right; font-size: .8em; } #TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; } #TrivialReporter .suite .suite { margin: 5px; } #TrivialReporter .suite.passed { background-color: #dfd; } #TrivialReporter .suite.failed { background-color: #fdd; } #TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; } #TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; } #TrivialReporter .spec.failed { background-color: #fbb; border-color: red; } #TrivialReporter .spec.passed { background-color: #bfb; border-color: green; } #TrivialReporter .spec.skipped { background-color: #bbb; } #TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; } #TrivialReporter .passed { background-color: #cfc; display: none; } #TrivialReporter .failed { background-color: #fbb; } #TrivialReporter .skipped { color: #777; background-color: #eee; display: none; } #TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; } #TrivialReporter .resultMessage .mismatch { color: black; } #TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; } #TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; } #TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; } #TrivialReporter #jasmine_content { position: fixed; right: 100%; } #TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; } modestmaps-js-3.3.6/test/lib/jasmine-1.2.0.rc3/jasmine.js000066400000000000000000002060731210227260500225770ustar00rootroot00000000000000var isCommonJS = typeof window == "undefined"; /** * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework. * * @namespace */ var jasmine = {}; if (isCommonJS) exports.jasmine = jasmine; /** * @private */ jasmine.unimplementedMethod_ = function() { throw new Error("unimplemented method"); }; /** * Use jasmine.undefined instead of undefined, since undefined is just * a plain old variable and may be redefined by somebody else. * * @private */ jasmine.undefined = jasmine.___undefined___; /** * Show diagnostic messages in the console if set to true * */ jasmine.VERBOSE = false; /** * Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed. * */ jasmine.DEFAULT_UPDATE_INTERVAL = 250; /** * Default timeout interval in milliseconds for waitsFor() blocks. */ jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000; jasmine.getGlobal = function() { function getGlobal() { return this; } return getGlobal(); }; /** * Allows for bound functions to be compared. Internal use only. * * @ignore * @private * @param base {Object} bound 'this' for the function * @param name {Function} function to find */ jasmine.bindOriginal_ = function(base, name) { var original = base[name]; if (original.apply) { return function() { return original.apply(base, arguments); }; } else { // IE support return jasmine.getGlobal()[name]; } }; jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout'); jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout'); jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval'); jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval'); jasmine.MessageResult = function(values) { this.type = 'log'; this.values = values; this.trace = new Error(); // todo: test better }; jasmine.MessageResult.prototype.toString = function() { var text = ""; for (var i = 0; i < this.values.length; i++) { if (i > 0) text += " "; if (jasmine.isString_(this.values[i])) { text += this.values[i]; } else { text += jasmine.pp(this.values[i]); } } return text; }; jasmine.ExpectationResult = function(params) { this.type = 'expect'; this.matcherName = params.matcherName; this.passed_ = params.passed; this.expected = params.expected; this.actual = params.actual; this.message = this.passed_ ? 'Passed.' : params.message; var trace = (params.trace || new Error(this.message)); this.trace = this.passed_ ? '' : trace; }; jasmine.ExpectationResult.prototype.toString = function () { return this.message; }; jasmine.ExpectationResult.prototype.passed = function () { return this.passed_; }; /** * Getter for the Jasmine environment. Ensures one gets created */ jasmine.getEnv = function() { var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env(); return env; }; /** * @ignore * @private * @param value * @returns {Boolean} */ jasmine.isArray_ = function(value) { return jasmine.isA_("Array", value); }; /** * @ignore * @private * @param value * @returns {Boolean} */ jasmine.isString_ = function(value) { return jasmine.isA_("String", value); }; /** * @ignore * @private * @param value * @returns {Boolean} */ jasmine.isNumber_ = function(value) { return jasmine.isA_("Number", value); }; /** * @ignore * @private * @param {String} typeName * @param value * @returns {Boolean} */ jasmine.isA_ = function(typeName, value) { return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; }; /** * Pretty printer for expecations. Takes any object and turns it into a human-readable string. * * @param value {Object} an object to be outputted * @returns {String} */ jasmine.pp = function(value) { var stringPrettyPrinter = new jasmine.StringPrettyPrinter(); stringPrettyPrinter.format(value); return stringPrettyPrinter.string; }; /** * Returns true if the object is a DOM Node. * * @param {Object} obj object to check * @returns {Boolean} */ jasmine.isDomNode = function(obj) { return obj.nodeType > 0; }; /** * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter. * * @example * // don't care about which function is passed in, as long as it's a function * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function)); * * @param {Class} clazz * @returns matchable object of the type clazz */ jasmine.any = function(clazz) { return new jasmine.Matchers.Any(clazz); }; /** * Returns a matchable subset of a JSON object. For use in expectations when you don't care about all of the * attributes on the object. * * @example * // don't care about any other attributes than foo. * expect(mySpy).toHaveBeenCalledWith(jasmine.objectContaining({foo: "bar"}); * * @param sample {Object} sample * @returns matchable object for the sample */ jasmine.objectContaining = function (sample) { return new jasmine.Matchers.ObjectContaining(sample); }; /** * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks. * * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine * expectation syntax. Spies can be checked if they were called or not and what the calling params were. * * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs). * * Spies are torn down at the end of every spec. * * Note: Do not call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj. * * @example * // a stub * var myStub = jasmine.createSpy('myStub'); // can be used anywhere * * // spy example * var foo = { * not: function(bool) { return !bool; } * } * * // actual foo.not will not be called, execution stops * spyOn(foo, 'not'); // foo.not spied upon, execution will continue to implementation * spyOn(foo, 'not').andCallThrough(); * * // fake example * var foo = { * not: function(bool) { return !bool; } * } * * // foo.not(val) will return val * spyOn(foo, 'not').andCallFake(function(value) {return value;}); * * // mock example * foo.not(7 == 7); * expect(foo.not).toHaveBeenCalled(); * expect(foo.not).toHaveBeenCalledWith(true); * * @constructor * @see spyOn, jasmine.createSpy, jasmine.createSpyObj * @param {String} name */ jasmine.Spy = function(name) { /** * The name of the spy, if provided. */ this.identity = name || 'unknown'; /** * Is this Object a spy? */ this.isSpy = true; /** * The actual function this spy stubs. */ this.plan = function() { }; /** * Tracking of the most recent call to the spy. * @example * var mySpy = jasmine.createSpy('foo'); * mySpy(1, 2); * mySpy.mostRecentCall.args = [1, 2]; */ this.mostRecentCall = {}; /** * Holds arguments for each call to the spy, indexed by call count * @example * var mySpy = jasmine.createSpy('foo'); * mySpy(1, 2); * mySpy(7, 8); * mySpy.mostRecentCall.args = [7, 8]; * mySpy.argsForCall[0] = [1, 2]; * mySpy.argsForCall[1] = [7, 8]; */ this.argsForCall = []; this.calls = []; }; /** * Tells a spy to call through to the actual implemenatation. * * @example * var foo = { * bar: function() { // do some stuff } * } * * // defining a spy on an existing property: foo.bar * spyOn(foo, 'bar').andCallThrough(); */ jasmine.Spy.prototype.andCallThrough = function() { this.plan = this.originalValue; return this; }; /** * For setting the return value of a spy. * * @example * // defining a spy from scratch: foo() returns 'baz' * var foo = jasmine.createSpy('spy on foo').andReturn('baz'); * * // defining a spy on an existing property: foo.bar() returns 'baz' * spyOn(foo, 'bar').andReturn('baz'); * * @param {Object} value */ jasmine.Spy.prototype.andReturn = function(value) { this.plan = function() { return value; }; return this; }; /** * For throwing an exception when a spy is called. * * @example * // defining a spy from scratch: foo() throws an exception w/ message 'ouch' * var foo = jasmine.createSpy('spy on foo').andThrow('baz'); * * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch' * spyOn(foo, 'bar').andThrow('baz'); * * @param {String} exceptionMsg */ jasmine.Spy.prototype.andThrow = function(exceptionMsg) { this.plan = function() { throw exceptionMsg; }; return this; }; /** * Calls an alternate implementation when a spy is called. * * @example * var baz = function() { * // do some stuff, return something * } * // defining a spy from scratch: foo() calls the function baz * var foo = jasmine.createSpy('spy on foo').andCall(baz); * * // defining a spy on an existing property: foo.bar() calls an anonymnous function * spyOn(foo, 'bar').andCall(function() { return 'baz';} ); * * @param {Function} fakeFunc */ jasmine.Spy.prototype.andCallFake = function(fakeFunc) { this.plan = fakeFunc; return this; }; /** * Resets all of a spy's the tracking variables so that it can be used again. * * @example * spyOn(foo, 'bar'); * * foo.bar(); * * expect(foo.bar.callCount).toEqual(1); * * foo.bar.reset(); * * expect(foo.bar.callCount).toEqual(0); */ jasmine.Spy.prototype.reset = function() { this.wasCalled = false; this.callCount = 0; this.argsForCall = []; this.calls = []; this.mostRecentCall = {}; }; jasmine.createSpy = function(name) { var spyObj = function() { spyObj.wasCalled = true; spyObj.callCount++; var args = jasmine.util.argsToArray(arguments); spyObj.mostRecentCall.object = this; spyObj.mostRecentCall.args = args; spyObj.argsForCall.push(args); spyObj.calls.push({object: this, args: args}); return spyObj.plan.apply(this, arguments); }; var spy = new jasmine.Spy(name); for (var prop in spy) { spyObj[prop] = spy[prop]; } spyObj.reset(); return spyObj; }; /** * Determines whether an object is a spy. * * @param {jasmine.Spy|Object} putativeSpy * @returns {Boolean} */ jasmine.isSpy = function(putativeSpy) { return putativeSpy && putativeSpy.isSpy; }; /** * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something * large in one call. * * @param {String} baseName name of spy class * @param {Array} methodNames array of names of methods to make spies */ jasmine.createSpyObj = function(baseName, methodNames) { if (!jasmine.isArray_(methodNames) || methodNames.length === 0) { throw new Error('createSpyObj requires a non-empty array of method names to create spies for'); } var obj = {}; for (var i = 0; i < methodNames.length; i++) { obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]); } return obj; }; /** * All parameters are pretty-printed and concatenated together, then written to the current spec's output. * * Be careful not to leave calls to jasmine.log in production code. */ jasmine.log = function() { var spec = jasmine.getEnv().currentSpec; spec.log.apply(spec, arguments); }; /** * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy. * * @example * // spy example * var foo = { * not: function(bool) { return !bool; } * } * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops * * @see jasmine.createSpy * @param obj * @param methodName * @returns a Jasmine spy that can be chained with all spy methods */ var spyOn = function(obj, methodName) { return jasmine.getEnv().currentSpec.spyOn(obj, methodName); }; if (isCommonJS) exports.spyOn = spyOn; /** * Creates a Jasmine spec that will be added to the current suite. * * // TODO: pending tests * * @example * it('should be true', function() { * expect(true).toEqual(true); * }); * * @param {String} desc description of this specification * @param {Function} func defines the preconditions and expectations of the spec */ var it = function(desc, func) { return jasmine.getEnv().it(desc, func); }; if (isCommonJS) exports.it = it; /** * Creates a disabled Jasmine spec. * * A convenience method that allows existing specs to be disabled temporarily during development. * * @param {String} desc description of this specification * @param {Function} func defines the preconditions and expectations of the spec */ var xit = function(desc, func) { return jasmine.getEnv().xit(desc, func); }; if (isCommonJS) exports.xit = xit; /** * Starts a chain for a Jasmine expectation. * * It is passed an Object that is the actual value and should chain to one of the many * jasmine.Matchers functions. * * @param {Object} actual Actual value to test against and expected value */ var expect = function(actual) { return jasmine.getEnv().currentSpec.expect(actual); }; if (isCommonJS) exports.expect = expect; /** * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs. * * @param {Function} func Function that defines part of a jasmine spec. */ var runs = function(func) { jasmine.getEnv().currentSpec.runs(func); }; if (isCommonJS) exports.runs = runs; /** * Waits a fixed time period before moving to the next block. * * @deprecated Use waitsFor() instead * @param {Number} timeout milliseconds to wait */ var waits = function(timeout) { jasmine.getEnv().currentSpec.waits(timeout); }; if (isCommonJS) exports.waits = waits; /** * Waits for the latchFunction to return true before proceeding to the next block. * * @param {Function} latchFunction * @param {String} optional_timeoutMessage * @param {Number} optional_timeout */ var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments); }; if (isCommonJS) exports.waitsFor = waitsFor; /** * A function that is called before each spec in a suite. * * Used for spec setup, including validating assumptions. * * @param {Function} beforeEachFunction */ var beforeEach = function(beforeEachFunction) { jasmine.getEnv().beforeEach(beforeEachFunction); }; if (isCommonJS) exports.beforeEach = beforeEach; /** * A function that is called after each spec in a suite. * * Used for restoring any state that is hijacked during spec execution. * * @param {Function} afterEachFunction */ var afterEach = function(afterEachFunction) { jasmine.getEnv().afterEach(afterEachFunction); }; if (isCommonJS) exports.afterEach = afterEach; /** * Defines a suite of specifications. * * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization * of setup in some tests. * * @example * // TODO: a simple suite * * // TODO: a simple suite with a nested describe block * * @param {String} description A string, usually the class under test. * @param {Function} specDefinitions function that defines several specs. */ var describe = function(description, specDefinitions) { return jasmine.getEnv().describe(description, specDefinitions); }; if (isCommonJS) exports.describe = describe; /** * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development. * * @param {String} description A string, usually the class under test. * @param {Function} specDefinitions function that defines several specs. */ var xdescribe = function(description, specDefinitions) { return jasmine.getEnv().xdescribe(description, specDefinitions); }; if (isCommonJS) exports.xdescribe = xdescribe; // Provide the XMLHttpRequest class for IE 5.x-6.x: jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() { function tryIt(f) { try { return f(); } catch(e) { } return null; } var xhr = tryIt(function() { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); }) || tryIt(function() { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); }) || tryIt(function() { return new ActiveXObject("Msxml2.XMLHTTP"); }) || tryIt(function() { return new ActiveXObject("Microsoft.XMLHTTP"); }); if (!xhr) throw new Error("This browser does not support XMLHttpRequest."); return xhr; } : XMLHttpRequest; /** * @namespace */ jasmine.util = {}; /** * Declare that a child class inherit it's prototype from the parent class. * * @private * @param {Function} childClass * @param {Function} parentClass */ jasmine.util.inherit = function(childClass, parentClass) { /** * @private */ var subclass = function() { }; subclass.prototype = parentClass.prototype; childClass.prototype = new subclass(); }; jasmine.util.formatException = function(e) { var lineNumber; if (e.line) { lineNumber = e.line; } else if (e.lineNumber) { lineNumber = e.lineNumber; } var file; if (e.sourceURL) { file = e.sourceURL; } else if (e.fileName) { file = e.fileName; } var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString(); if (file && lineNumber) { message += ' in ' + file + ' (line ' + lineNumber + ')'; } return message; }; jasmine.util.htmlEscape = function(str) { if (!str) return str; return str.replace(/&/g, '&') .replace(//g, '>'); }; jasmine.util.argsToArray = function(args) { var arrayOfArgs = []; for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]); return arrayOfArgs; }; jasmine.util.extend = function(destination, source) { for (var property in source) destination[property] = source[property]; return destination; }; /** * Environment for Jasmine * * @constructor */ jasmine.Env = function() { this.currentSpec = null; this.currentSuite = null; this.currentRunner_ = new jasmine.Runner(this); this.reporter = new jasmine.MultiReporter(); this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL; this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL; this.lastUpdate = 0; this.specFilter = function() { return true; }; this.nextSpecId_ = 0; this.nextSuiteId_ = 0; this.equalityTesters_ = []; // wrap matchers this.matchersClass = function() { jasmine.Matchers.apply(this, arguments); }; jasmine.util.inherit(this.matchersClass, jasmine.Matchers); jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass); }; jasmine.Env.prototype.setTimeout = jasmine.setTimeout; jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout; jasmine.Env.prototype.setInterval = jasmine.setInterval; jasmine.Env.prototype.clearInterval = jasmine.clearInterval; /** * @returns an object containing jasmine version build info, if set. */ jasmine.Env.prototype.version = function () { if (jasmine.version_) { return jasmine.version_; } else { throw new Error('Version not set'); } }; /** * @returns string containing jasmine version build info, if set. */ jasmine.Env.prototype.versionString = function() { if (!jasmine.version_) { return "version unknown"; } var version = this.version(); var versionString = version.major + "." + version.minor + "." + version.build; if (version.release_candidate) { versionString += ".rc" + version.release_candidate; } versionString += " revision " + version.revision; return versionString; }; /** * @returns a sequential integer starting at 0 */ jasmine.Env.prototype.nextSpecId = function () { return this.nextSpecId_++; }; /** * @returns a sequential integer starting at 0 */ jasmine.Env.prototype.nextSuiteId = function () { return this.nextSuiteId_++; }; /** * Register a reporter to receive status updates from Jasmine. * @param {jasmine.Reporter} reporter An object which will receive status updates. */ jasmine.Env.prototype.addReporter = function(reporter) { this.reporter.addReporter(reporter); }; jasmine.Env.prototype.execute = function() { this.currentRunner_.execute(); }; jasmine.Env.prototype.describe = function(description, specDefinitions) { var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite); var parentSuite = this.currentSuite; if (parentSuite) { parentSuite.add(suite); } else { this.currentRunner_.add(suite); } this.currentSuite = suite; var declarationError = null; try { specDefinitions.call(suite); } catch(e) { declarationError = e; } if (declarationError) { this.it("encountered a declaration exception", function() { throw declarationError; }); } this.currentSuite = parentSuite; return suite; }; jasmine.Env.prototype.beforeEach = function(beforeEachFunction) { if (this.currentSuite) { this.currentSuite.beforeEach(beforeEachFunction); } else { this.currentRunner_.beforeEach(beforeEachFunction); } }; jasmine.Env.prototype.currentRunner = function () { return this.currentRunner_; }; jasmine.Env.prototype.afterEach = function(afterEachFunction) { if (this.currentSuite) { this.currentSuite.afterEach(afterEachFunction); } else { this.currentRunner_.afterEach(afterEachFunction); } }; jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) { return { execute: function() { } }; }; jasmine.Env.prototype.it = function(description, func) { var spec = new jasmine.Spec(this, this.currentSuite, description); this.currentSuite.add(spec); this.currentSpec = spec; if (func) { spec.runs(func); } return spec; }; jasmine.Env.prototype.xit = function(desc, func) { return { id: this.nextSpecId(), runs: function() { } }; }; jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) { if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) { return true; } a.__Jasmine_been_here_before__ = b; b.__Jasmine_been_here_before__ = a; var hasKey = function(obj, keyName) { return obj !== null && obj[keyName] !== jasmine.undefined; }; for (var property in b) { if (!hasKey(a, property) && hasKey(b, property)) { mismatchKeys.push("expected has key '" + property + "', but missing from actual."); } } for (property in a) { if (!hasKey(b, property) && hasKey(a, property)) { mismatchKeys.push("expected missing key '" + property + "', but present in actual."); } } for (property in b) { if (property == '__Jasmine_been_here_before__') continue; if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) { mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual."); } } if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) { mismatchValues.push("arrays were not the same length"); } delete a.__Jasmine_been_here_before__; delete b.__Jasmine_been_here_before__; return (mismatchKeys.length === 0 && mismatchValues.length === 0); }; jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) { mismatchKeys = mismatchKeys || []; mismatchValues = mismatchValues || []; for (var i = 0; i < this.equalityTesters_.length; i++) { var equalityTester = this.equalityTesters_[i]; var result = equalityTester(a, b, this, mismatchKeys, mismatchValues); if (result !== jasmine.undefined) return result; } if (a === b) return true; if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) { return (a == jasmine.undefined && b == jasmine.undefined); } if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) { return a === b; } if (a instanceof Date && b instanceof Date) { return a.getTime() == b.getTime(); } if (a.jasmineMatches) { return a.jasmineMatches(b); } if (b.jasmineMatches) { return b.jasmineMatches(a); } if (a instanceof jasmine.Matchers.ObjectContaining) { return a.matches(b); } if (b instanceof jasmine.Matchers.ObjectContaining) { return b.matches(a); } if (jasmine.isString_(a) && jasmine.isString_(b)) { return (a == b); } if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) { return (a == b); } if (typeof a === "object" && typeof b === "object") { return this.compareObjects_(a, b, mismatchKeys, mismatchValues); } //Straight check return (a === b); }; jasmine.Env.prototype.contains_ = function(haystack, needle) { if (jasmine.isArray_(haystack)) { for (var i = 0; i < haystack.length; i++) { if (this.equals_(haystack[i], needle)) return true; } return false; } return haystack.indexOf(needle) >= 0; }; jasmine.Env.prototype.addEqualityTester = function(equalityTester) { this.equalityTesters_.push(equalityTester); }; /** No-op base class for Jasmine reporters. * * @constructor */ jasmine.Reporter = function() { }; //noinspection JSUnusedLocalSymbols jasmine.Reporter.prototype.reportRunnerStarting = function(runner) { }; //noinspection JSUnusedLocalSymbols jasmine.Reporter.prototype.reportRunnerResults = function(runner) { }; //noinspection JSUnusedLocalSymbols jasmine.Reporter.prototype.reportSuiteResults = function(suite) { }; //noinspection JSUnusedLocalSymbols jasmine.Reporter.prototype.reportSpecStarting = function(spec) { }; //noinspection JSUnusedLocalSymbols jasmine.Reporter.prototype.reportSpecResults = function(spec) { }; //noinspection JSUnusedLocalSymbols jasmine.Reporter.prototype.log = function(str) { }; /** * Blocks are functions with executable code that make up a spec. * * @constructor * @param {jasmine.Env} env * @param {Function} func * @param {jasmine.Spec} spec */ jasmine.Block = function(env, func, spec) { this.env = env; this.func = func; this.spec = spec; }; jasmine.Block.prototype.execute = function(onComplete) { try { this.func.apply(this.spec); } catch (e) { this.spec.fail(e); } onComplete(); }; /** JavaScript API reporter. * * @constructor */ jasmine.JsApiReporter = function() { this.started = false; this.finished = false; this.suites_ = []; this.results_ = {}; }; jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) { this.started = true; var suites = runner.topLevelSuites(); for (var i = 0; i < suites.length; i++) { var suite = suites[i]; this.suites_.push(this.summarize_(suite)); } }; jasmine.JsApiReporter.prototype.suites = function() { return this.suites_; }; jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) { var isSuite = suiteOrSpec instanceof jasmine.Suite; var summary = { id: suiteOrSpec.id, name: suiteOrSpec.description, type: isSuite ? 'suite' : 'spec', children: [] }; if (isSuite) { var children = suiteOrSpec.children(); for (var i = 0; i < children.length; i++) { summary.children.push(this.summarize_(children[i])); } } return summary; }; jasmine.JsApiReporter.prototype.results = function() { return this.results_; }; jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) { return this.results_[specId]; }; //noinspection JSUnusedLocalSymbols jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) { this.finished = true; }; //noinspection JSUnusedLocalSymbols jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) { }; //noinspection JSUnusedLocalSymbols jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) { this.results_[spec.id] = { messages: spec.results().getItems(), result: spec.results().failedCount > 0 ? "failed" : "passed" }; }; //noinspection JSUnusedLocalSymbols jasmine.JsApiReporter.prototype.log = function(str) { }; jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){ var results = {}; for (var i = 0; i < specIds.length; i++) { var specId = specIds[i]; results[specId] = this.summarizeResult_(this.results_[specId]); } return results; }; jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){ var summaryMessages = []; var messagesLength = result.messages.length; for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) { var resultMessage = result.messages[messageIndex]; summaryMessages.push({ text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined, passed: resultMessage.passed ? resultMessage.passed() : true, type: resultMessage.type, message: resultMessage.message, trace: { stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined } }); } return { result : result.result, messages : summaryMessages }; }; /** * @constructor * @param {jasmine.Env} env * @param actual * @param {jasmine.Spec} spec */ jasmine.Matchers = function(env, actual, spec, opt_isNot) { this.env = env; this.actual = actual; this.spec = spec; this.isNot = opt_isNot || false; this.reportWasCalled_ = false; }; // todo: @deprecated as of Jasmine 0.11, remove soon [xw] jasmine.Matchers.pp = function(str) { throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!"); }; // todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw] jasmine.Matchers.prototype.report = function(result, failing_message, details) { throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs"); }; jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) { for (var methodName in prototype) { if (methodName == 'report') continue; var orig = prototype[methodName]; matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig); } }; jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) { return function() { var matcherArgs = jasmine.util.argsToArray(arguments); var result = matcherFunction.apply(this, arguments); if (this.isNot) { result = !result; } if (this.reportWasCalled_) return result; var message; if (!result) { if (this.message) { message = this.message.apply(this, arguments); if (jasmine.isArray_(message)) { message = message[this.isNot ? 1 : 0]; } } else { var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate; if (matcherArgs.length > 0) { for (var i = 0; i < matcherArgs.length; i++) { if (i > 0) message += ","; message += " " + jasmine.pp(matcherArgs[i]); } } message += "."; } } var expectationResult = new jasmine.ExpectationResult({ matcherName: matcherName, passed: result, expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0], actual: this.actual, message: message }); this.spec.addMatcherResult(expectationResult); return jasmine.undefined; }; }; /** * toBe: compares the actual to the expected using === * @param expected */ jasmine.Matchers.prototype.toBe = function(expected) { return this.actual === expected; }; /** * toNotBe: compares the actual to the expected using !== * @param expected * @deprecated as of 1.0. Use not.toBe() instead. */ jasmine.Matchers.prototype.toNotBe = function(expected) { return this.actual !== expected; }; /** * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc. * * @param expected */ jasmine.Matchers.prototype.toEqual = function(expected) { return this.env.equals_(this.actual, expected); }; /** * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual * @param expected * @deprecated as of 1.0. Use not.toEqual() instead. */ jasmine.Matchers.prototype.toNotEqual = function(expected) { return !this.env.equals_(this.actual, expected); }; /** * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes * a pattern or a String. * * @param expected */ jasmine.Matchers.prototype.toMatch = function(expected) { return new RegExp(expected).test(this.actual); }; /** * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch * @param expected * @deprecated as of 1.0. Use not.toMatch() instead. */ jasmine.Matchers.prototype.toNotMatch = function(expected) { return !(new RegExp(expected).test(this.actual)); }; /** * Matcher that compares the actual to jasmine.undefined. */ jasmine.Matchers.prototype.toBeDefined = function() { return (this.actual !== jasmine.undefined); }; /** * Matcher that compares the actual to jasmine.undefined. */ jasmine.Matchers.prototype.toBeUndefined = function() { return (this.actual === jasmine.undefined); }; /** * Matcher that compares the actual to null. */ jasmine.Matchers.prototype.toBeNull = function() { return (this.actual === null); }; /** * Matcher that boolean not-nots the actual. */ jasmine.Matchers.prototype.toBeTruthy = function() { return !!this.actual; }; /** * Matcher that boolean nots the actual. */ jasmine.Matchers.prototype.toBeFalsy = function() { return !this.actual; }; /** * Matcher that checks to see if the actual, a Jasmine spy, was called. */ jasmine.Matchers.prototype.toHaveBeenCalled = function() { if (arguments.length > 0) { throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith'); } if (!jasmine.isSpy(this.actual)) { throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); } this.message = function() { return [ "Expected spy " + this.actual.identity + " to have been called.", "Expected spy " + this.actual.identity + " not to have been called." ]; }; return this.actual.wasCalled; }; /** @deprecated Use expect(xxx).toHaveBeenCalled() instead */ jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled; /** * Matcher that checks to see if the actual, a Jasmine spy, was not called. * * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead */ jasmine.Matchers.prototype.wasNotCalled = function() { if (arguments.length > 0) { throw new Error('wasNotCalled does not take arguments'); } if (!jasmine.isSpy(this.actual)) { throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); } this.message = function() { return [ "Expected spy " + this.actual.identity + " to not have been called.", "Expected spy " + this.actual.identity + " to have been called." ]; }; return !this.actual.wasCalled; }; /** * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters. * * @example * */ jasmine.Matchers.prototype.toHaveBeenCalledWith = function() { var expectedArgs = jasmine.util.argsToArray(arguments); if (!jasmine.isSpy(this.actual)) { throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); } this.message = function() { if (this.actual.callCount === 0) { // todo: what should the failure message for .not.toHaveBeenCalledWith() be? is this right? test better. [xw] return [ "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.", "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but it was." ]; } else { return [ "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall), "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall) ]; } }; return this.env.contains_(this.actual.argsForCall, expectedArgs); }; /** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */ jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith; /** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */ jasmine.Matchers.prototype.wasNotCalledWith = function() { var expectedArgs = jasmine.util.argsToArray(arguments); if (!jasmine.isSpy(this.actual)) { throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); } this.message = function() { return [ "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was", "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was" ]; }; return !this.env.contains_(this.actual.argsForCall, expectedArgs); }; /** * Matcher that checks that the expected item is an element in the actual Array. * * @param {Object} expected */ jasmine.Matchers.prototype.toContain = function(expected) { return this.env.contains_(this.actual, expected); }; /** * Matcher that checks that the expected item is NOT an element in the actual Array. * * @param {Object} expected * @deprecated as of 1.0. Use not.toContain() instead. */ jasmine.Matchers.prototype.toNotContain = function(expected) { return !this.env.contains_(this.actual, expected); }; jasmine.Matchers.prototype.toBeLessThan = function(expected) { return this.actual < expected; }; jasmine.Matchers.prototype.toBeGreaterThan = function(expected) { return this.actual > expected; }; /** * Matcher that checks that the expected item is equal to the actual item * up to a given level of decimal precision (default 2). * * @param {Number} expected * @param {Number} precision */ jasmine.Matchers.prototype.toBeCloseTo = function(expected, precision) { if (!(precision === 0)) { precision = precision || 2; } var multiplier = Math.pow(10, precision); var actual = Math.round(this.actual * multiplier); expected = Math.round(expected * multiplier); return expected == actual; }; /** * Matcher that checks that the expected exception was thrown by the actual. * * @param {String} expected */ jasmine.Matchers.prototype.toThrow = function(expected) { var result = false; var exception; if (typeof this.actual != 'function') { throw new Error('Actual is not a function'); } try { this.actual(); } catch (e) { exception = e; } if (exception) { result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected)); } var not = this.isNot ? "not " : ""; this.message = function() { if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) { return ["Expected function " + not + "to throw", expected ? expected.message || expected : "an exception", ", but it threw", exception.message || exception].join(' '); } else { return "Expected function to throw an exception."; } }; return result; }; jasmine.Matchers.Any = function(expectedClass) { this.expectedClass = expectedClass; }; jasmine.Matchers.Any.prototype.jasmineMatches = function(other) { if (this.expectedClass == String) { return typeof other == 'string' || other instanceof String; } if (this.expectedClass == Number) { return typeof other == 'number' || other instanceof Number; } if (this.expectedClass == Function) { return typeof other == 'function' || other instanceof Function; } if (this.expectedClass == Object) { return typeof other == 'object'; } return other instanceof this.expectedClass; }; jasmine.Matchers.Any.prototype.jasmineToString = function() { return ''; }; jasmine.Matchers.ObjectContaining = function (sample) { this.sample = sample; }; jasmine.Matchers.ObjectContaining.prototype.jasmineMatches = function(other, mismatchKeys, mismatchValues) { mismatchKeys = mismatchKeys || []; mismatchValues = mismatchValues || []; var env = jasmine.getEnv(); var hasKey = function(obj, keyName) { return obj != null && obj[keyName] !== jasmine.undefined; }; for (var property in this.sample) { if (!hasKey(other, property) && hasKey(this.sample, property)) { mismatchKeys.push("expected has key '" + property + "', but missing from actual."); } else if (!env.equals_(this.sample[property], other[property], mismatchKeys, mismatchValues)) { mismatchValues.push("'" + property + "' was '" + (other[property] ? jasmine.util.htmlEscape(other[property].toString()) : other[property]) + "' in expected, but was '" + (this.sample[property] ? jasmine.util.htmlEscape(this.sample[property].toString()) : this.sample[property]) + "' in actual."); } } return (mismatchKeys.length === 0 && mismatchValues.length === 0); }; jasmine.Matchers.ObjectContaining.prototype.jasmineToString = function () { return ""; }; // Mock setTimeout, clearTimeout // Contributed by Pivotal Computer Systems, www.pivotalsf.com jasmine.FakeTimer = function() { this.reset(); var self = this; self.setTimeout = function(funcToCall, millis) { self.timeoutsMade++; self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false); return self.timeoutsMade; }; self.setInterval = function(funcToCall, millis) { self.timeoutsMade++; self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true); return self.timeoutsMade; }; self.clearTimeout = function(timeoutKey) { self.scheduledFunctions[timeoutKey] = jasmine.undefined; }; self.clearInterval = function(timeoutKey) { self.scheduledFunctions[timeoutKey] = jasmine.undefined; }; }; jasmine.FakeTimer.prototype.reset = function() { this.timeoutsMade = 0; this.scheduledFunctions = {}; this.nowMillis = 0; }; jasmine.FakeTimer.prototype.tick = function(millis) { var oldMillis = this.nowMillis; var newMillis = oldMillis + millis; this.runFunctionsWithinRange(oldMillis, newMillis); this.nowMillis = newMillis; }; jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) { var scheduledFunc; var funcsToRun = []; for (var timeoutKey in this.scheduledFunctions) { scheduledFunc = this.scheduledFunctions[timeoutKey]; if (scheduledFunc != jasmine.undefined && scheduledFunc.runAtMillis >= oldMillis && scheduledFunc.runAtMillis <= nowMillis) { funcsToRun.push(scheduledFunc); this.scheduledFunctions[timeoutKey] = jasmine.undefined; } } if (funcsToRun.length > 0) { funcsToRun.sort(function(a, b) { return a.runAtMillis - b.runAtMillis; }); for (var i = 0; i < funcsToRun.length; ++i) { try { var funcToRun = funcsToRun[i]; this.nowMillis = funcToRun.runAtMillis; funcToRun.funcToCall(); if (funcToRun.recurring) { this.scheduleFunction(funcToRun.timeoutKey, funcToRun.funcToCall, funcToRun.millis, true); } } catch(e) { } } this.runFunctionsWithinRange(oldMillis, nowMillis); } }; jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) { this.scheduledFunctions[timeoutKey] = { runAtMillis: this.nowMillis + millis, funcToCall: funcToCall, recurring: recurring, timeoutKey: timeoutKey, millis: millis }; }; /** * @namespace */ jasmine.Clock = { defaultFakeTimer: new jasmine.FakeTimer(), reset: function() { jasmine.Clock.assertInstalled(); jasmine.Clock.defaultFakeTimer.reset(); }, tick: function(millis) { jasmine.Clock.assertInstalled(); jasmine.Clock.defaultFakeTimer.tick(millis); }, runFunctionsWithinRange: function(oldMillis, nowMillis) { jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis); }, scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) { jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring); }, useMock: function() { if (!jasmine.Clock.isInstalled()) { var spec = jasmine.getEnv().currentSpec; spec.after(jasmine.Clock.uninstallMock); jasmine.Clock.installMock(); } }, installMock: function() { jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer; }, uninstallMock: function() { jasmine.Clock.assertInstalled(); jasmine.Clock.installed = jasmine.Clock.real; }, real: { setTimeout: jasmine.getGlobal().setTimeout, clearTimeout: jasmine.getGlobal().clearTimeout, setInterval: jasmine.getGlobal().setInterval, clearInterval: jasmine.getGlobal().clearInterval }, assertInstalled: function() { if (!jasmine.Clock.isInstalled()) { throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()"); } }, isInstalled: function() { return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer; }, installed: null }; jasmine.Clock.installed = jasmine.Clock.real; //else for IE support jasmine.getGlobal().setTimeout = function(funcToCall, millis) { if (jasmine.Clock.installed.setTimeout.apply) { return jasmine.Clock.installed.setTimeout.apply(this, arguments); } else { return jasmine.Clock.installed.setTimeout(funcToCall, millis); } }; jasmine.getGlobal().setInterval = function(funcToCall, millis) { if (jasmine.Clock.installed.setInterval.apply) { return jasmine.Clock.installed.setInterval.apply(this, arguments); } else { return jasmine.Clock.installed.setInterval(funcToCall, millis); } }; jasmine.getGlobal().clearTimeout = function(timeoutKey) { if (jasmine.Clock.installed.clearTimeout.apply) { return jasmine.Clock.installed.clearTimeout.apply(this, arguments); } else { return jasmine.Clock.installed.clearTimeout(timeoutKey); } }; jasmine.getGlobal().clearInterval = function(timeoutKey) { if (jasmine.Clock.installed.clearTimeout.apply) { return jasmine.Clock.installed.clearInterval.apply(this, arguments); } else { return jasmine.Clock.installed.clearInterval(timeoutKey); } }; /** * @constructor */ jasmine.MultiReporter = function() { this.subReporters_ = []; }; jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter); jasmine.MultiReporter.prototype.addReporter = function(reporter) { this.subReporters_.push(reporter); }; (function() { var functionNames = [ "reportRunnerStarting", "reportRunnerResults", "reportSuiteResults", "reportSpecStarting", "reportSpecResults", "log" ]; for (var i = 0; i < functionNames.length; i++) { var functionName = functionNames[i]; jasmine.MultiReporter.prototype[functionName] = (function(functionName) { return function() { for (var j = 0; j < this.subReporters_.length; j++) { var subReporter = this.subReporters_[j]; if (subReporter[functionName]) { subReporter[functionName].apply(subReporter, arguments); } } }; })(functionName); } })(); /** * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults * * @constructor */ jasmine.NestedResults = function() { /** * The total count of results */ this.totalCount = 0; /** * Number of passed results */ this.passedCount = 0; /** * Number of failed results */ this.failedCount = 0; /** * Was this suite/spec skipped? */ this.skipped = false; /** * @ignore */ this.items_ = []; }; /** * Roll up the result counts. * * @param result */ jasmine.NestedResults.prototype.rollupCounts = function(result) { this.totalCount += result.totalCount; this.passedCount += result.passedCount; this.failedCount += result.failedCount; }; /** * Adds a log message. * @param values Array of message parts which will be concatenated later. */ jasmine.NestedResults.prototype.log = function(values) { this.items_.push(new jasmine.MessageResult(values)); }; /** * Getter for the results: message & results. */ jasmine.NestedResults.prototype.getItems = function() { return this.items_; }; /** * Adds a result, tracking counts (total, passed, & failed) * @param {jasmine.ExpectationResult|jasmine.NestedResults} result */ jasmine.NestedResults.prototype.addResult = function(result) { if (result.type != 'log') { if (result.items_) { this.rollupCounts(result); } else { this.totalCount++; if (result.passed()) { this.passedCount++; } else { this.failedCount++; } } } this.items_.push(result); }; /** * @returns {Boolean} True if everything below passed */ jasmine.NestedResults.prototype.passed = function() { return this.passedCount === this.totalCount; }; /** * Base class for pretty printing for expectation results. */ jasmine.PrettyPrinter = function() { this.ppNestLevel_ = 0; }; /** * Formats a value in a nice, human-readable string. * * @param value */ jasmine.PrettyPrinter.prototype.format = function(value) { if (this.ppNestLevel_ > 40) { throw new Error('jasmine.PrettyPrinter: format() nested too deeply!'); } this.ppNestLevel_++; try { if (value === jasmine.undefined) { this.emitScalar('undefined'); } else if (value === null) { this.emitScalar('null'); } else if (value === jasmine.getGlobal()) { this.emitScalar(''); } else if (value.jasmineToString) { this.emitScalar(value.jasmineToString()); } else if (typeof value === 'string') { this.emitString(value); } else if (jasmine.isSpy(value)) { this.emitScalar("spy on " + value.identity); } else if (value instanceof RegExp) { this.emitScalar(value.toString()); } else if (typeof value === 'function') { this.emitScalar('Function'); } else if (typeof value.nodeType === 'number') { this.emitScalar('HTMLNode'); } else if (value instanceof Date) { this.emitScalar('Date(' + value + ')'); } else if (value.__Jasmine_been_here_before__) { this.emitScalar(''); } else if (jasmine.isArray_(value) || typeof value == 'object') { value.__Jasmine_been_here_before__ = true; if (jasmine.isArray_(value)) { this.emitArray(value); } else { this.emitObject(value); } delete value.__Jasmine_been_here_before__; } else { this.emitScalar(value.toString()); } } finally { this.ppNestLevel_--; } }; jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) { for (var property in obj) { if (property == '__Jasmine_been_here_before__') continue; fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) !== jasmine.undefined && obj.__lookupGetter__(property) !== null) : false); } }; jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_; jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_; jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_; jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_; jasmine.StringPrettyPrinter = function() { jasmine.PrettyPrinter.call(this); this.string = ''; }; jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter); jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) { this.append(value); }; jasmine.StringPrettyPrinter.prototype.emitString = function(value) { this.append("'" + value + "'"); }; jasmine.StringPrettyPrinter.prototype.emitArray = function(array) { this.append('[ '); for (var i = 0; i < array.length; i++) { if (i > 0) { this.append(', '); } this.format(array[i]); } this.append(' ]'); }; jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) { var self = this; this.append('{ '); var first = true; this.iterateObject(obj, function(property, isGetter) { if (first) { first = false; } else { self.append(', '); } self.append(property); self.append(' : '); if (isGetter) { self.append(''); } else { self.format(obj[property]); } }); this.append(' }'); }; jasmine.StringPrettyPrinter.prototype.append = function(value) { this.string += value; }; jasmine.Queue = function(env) { this.env = env; this.blocks = []; this.running = false; this.index = 0; this.offset = 0; this.abort = false; }; jasmine.Queue.prototype.addBefore = function(block) { this.blocks.unshift(block); }; jasmine.Queue.prototype.add = function(block) { this.blocks.push(block); }; jasmine.Queue.prototype.insertNext = function(block) { this.blocks.splice((this.index + this.offset + 1), 0, block); this.offset++; }; jasmine.Queue.prototype.start = function(onComplete) { this.running = true; this.onComplete = onComplete; this.next_(); }; jasmine.Queue.prototype.isRunning = function() { return this.running; }; jasmine.Queue.LOOP_DONT_RECURSE = true; jasmine.Queue.prototype.next_ = function() { var self = this; var goAgain = true; while (goAgain) { goAgain = false; if (self.index < self.blocks.length && !this.abort) { var calledSynchronously = true; var completedSynchronously = false; var onComplete = function () { if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) { completedSynchronously = true; return; } if (self.blocks[self.index].abort) { self.abort = true; } self.offset = 0; self.index++; var now = new Date().getTime(); if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) { self.env.lastUpdate = now; self.env.setTimeout(function() { self.next_(); }, 0); } else { if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) { goAgain = true; } else { self.next_(); } } }; self.blocks[self.index].execute(onComplete); calledSynchronously = false; if (completedSynchronously) { onComplete(); } } else { self.running = false; if (self.onComplete) { self.onComplete(); } } } }; jasmine.Queue.prototype.results = function() { var results = new jasmine.NestedResults(); for (var i = 0; i < this.blocks.length; i++) { if (this.blocks[i].results) { results.addResult(this.blocks[i].results()); } } return results; }; /** * Runner * * @constructor * @param {jasmine.Env} env */ jasmine.Runner = function(env) { var self = this; self.env = env; self.queue = new jasmine.Queue(env); self.before_ = []; self.after_ = []; self.suites_ = []; }; jasmine.Runner.prototype.execute = function() { var self = this; if (self.env.reporter.reportRunnerStarting) { self.env.reporter.reportRunnerStarting(this); } self.queue.start(function () { self.finishCallback(); }); }; jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) { beforeEachFunction.typeName = 'beforeEach'; this.before_.splice(0,0,beforeEachFunction); }; jasmine.Runner.prototype.afterEach = function(afterEachFunction) { afterEachFunction.typeName = 'afterEach'; this.after_.splice(0,0,afterEachFunction); }; jasmine.Runner.prototype.finishCallback = function() { this.env.reporter.reportRunnerResults(this); }; jasmine.Runner.prototype.addSuite = function(suite) { this.suites_.push(suite); }; jasmine.Runner.prototype.add = function(block) { if (block instanceof jasmine.Suite) { this.addSuite(block); } this.queue.add(block); }; jasmine.Runner.prototype.specs = function () { var suites = this.suites(); var specs = []; for (var i = 0; i < suites.length; i++) { specs = specs.concat(suites[i].specs()); } return specs; }; jasmine.Runner.prototype.suites = function() { return this.suites_; }; jasmine.Runner.prototype.topLevelSuites = function() { var topLevelSuites = []; for (var i = 0; i < this.suites_.length; i++) { if (!this.suites_[i].parentSuite) { topLevelSuites.push(this.suites_[i]); } } return topLevelSuites; }; jasmine.Runner.prototype.results = function() { return this.queue.results(); }; /** * Internal representation of a Jasmine specification, or test. * * @constructor * @param {jasmine.Env} env * @param {jasmine.Suite} suite * @param {String} description */ jasmine.Spec = function(env, suite, description) { if (!env) { throw new Error('jasmine.Env() required'); } if (!suite) { throw new Error('jasmine.Suite() required'); } var spec = this; spec.id = env.nextSpecId ? env.nextSpecId() : null; spec.env = env; spec.suite = suite; spec.description = description; spec.queue = new jasmine.Queue(env); spec.afterCallbacks = []; spec.spies_ = []; spec.results_ = new jasmine.NestedResults(); spec.results_.description = description; spec.matchersClass = null; }; jasmine.Spec.prototype.getFullName = function() { return this.suite.getFullName() + ' ' + this.description + '.'; }; jasmine.Spec.prototype.results = function() { return this.results_; }; /** * All parameters are pretty-printed and concatenated together, then written to the spec's output. * * Be careful not to leave calls to jasmine.log in production code. */ jasmine.Spec.prototype.log = function() { return this.results_.log(arguments); }; jasmine.Spec.prototype.runs = function (func) { var block = new jasmine.Block(this.env, func, this); this.addToQueue(block); return this; }; jasmine.Spec.prototype.addToQueue = function (block) { if (this.queue.isRunning()) { this.queue.insertNext(block); } else { this.queue.add(block); } }; /** * @param {jasmine.ExpectationResult} result */ jasmine.Spec.prototype.addMatcherResult = function(result) { this.results_.addResult(result); }; jasmine.Spec.prototype.expect = function(actual) { var positive = new (this.getMatchersClass_())(this.env, actual, this); positive.not = new (this.getMatchersClass_())(this.env, actual, this, true); return positive; }; /** * Waits a fixed time period before moving to the next block. * * @deprecated Use waitsFor() instead * @param {Number} timeout milliseconds to wait */ jasmine.Spec.prototype.waits = function(timeout) { var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this); this.addToQueue(waitsFunc); return this; }; /** * Waits for the latchFunction to return true before proceeding to the next block. * * @param {Function} latchFunction * @param {String} optional_timeoutMessage * @param {Number} optional_timeout */ jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { var latchFunction_ = null; var optional_timeoutMessage_ = null; var optional_timeout_ = null; for (var i = 0; i < arguments.length; i++) { var arg = arguments[i]; switch (typeof arg) { case 'function': latchFunction_ = arg; break; case 'string': optional_timeoutMessage_ = arg; break; case 'number': optional_timeout_ = arg; break; } } var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this); this.addToQueue(waitsForFunc); return this; }; jasmine.Spec.prototype.fail = function (e) { var expectationResult = new jasmine.ExpectationResult({ passed: false, message: e ? jasmine.util.formatException(e) : 'Exception', trace: { stack: e.stack } }); this.results_.addResult(expectationResult); }; jasmine.Spec.prototype.getMatchersClass_ = function() { return this.matchersClass || this.env.matchersClass; }; jasmine.Spec.prototype.addMatchers = function(matchersPrototype) { var parent = this.getMatchersClass_(); var newMatchersClass = function() { parent.apply(this, arguments); }; jasmine.util.inherit(newMatchersClass, parent); jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass); this.matchersClass = newMatchersClass; }; jasmine.Spec.prototype.finishCallback = function() { this.env.reporter.reportSpecResults(this); }; jasmine.Spec.prototype.finish = function(onComplete) { this.removeAllSpies(); this.finishCallback(); if (onComplete) { onComplete(); } }; jasmine.Spec.prototype.after = function(doAfter) { if (this.queue.isRunning()) { this.queue.add(new jasmine.Block(this.env, doAfter, this)); } else { this.afterCallbacks.unshift(doAfter); } }; jasmine.Spec.prototype.execute = function(onComplete) { var spec = this; if (!spec.env.specFilter(spec)) { spec.results_.skipped = true; spec.finish(onComplete); return; } this.env.reporter.reportSpecStarting(this); spec.env.currentSpec = spec; spec.addBeforesAndAftersToQueue(); spec.queue.start(function () { spec.finish(onComplete); }); }; jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() { var runner = this.env.currentRunner(); var i; for (var suite = this.suite; suite; suite = suite.parentSuite) { for (i = 0; i < suite.before_.length; i++) { this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this)); } } for (i = 0; i < runner.before_.length; i++) { this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this)); } for (i = 0; i < this.afterCallbacks.length; i++) { this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this)); } for (suite = this.suite; suite; suite = suite.parentSuite) { for (i = 0; i < suite.after_.length; i++) { this.queue.add(new jasmine.Block(this.env, suite.after_[i], this)); } } for (i = 0; i < runner.after_.length; i++) { this.queue.add(new jasmine.Block(this.env, runner.after_[i], this)); } }; jasmine.Spec.prototype.explodes = function() { throw 'explodes function should not have been called'; }; jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) { if (obj == jasmine.undefined) { throw "spyOn could not find an object to spy upon for " + methodName + "()"; } if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) { throw methodName + '() method does not exist'; } if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) { throw new Error(methodName + ' has already been spied upon'); } var spyObj = jasmine.createSpy(methodName); this.spies_.push(spyObj); spyObj.baseObj = obj; spyObj.methodName = methodName; spyObj.originalValue = obj[methodName]; obj[methodName] = spyObj; return spyObj; }; jasmine.Spec.prototype.removeAllSpies = function() { for (var i = 0; i < this.spies_.length; i++) { var spy = this.spies_[i]; spy.baseObj[spy.methodName] = spy.originalValue; } this.spies_ = []; }; /** * Internal representation of a Jasmine suite. * * @constructor * @param {jasmine.Env} env * @param {String} description * @param {Function} specDefinitions * @param {jasmine.Suite} parentSuite */ jasmine.Suite = function(env, description, specDefinitions, parentSuite) { var self = this; self.id = env.nextSuiteId ? env.nextSuiteId() : null; self.description = description; self.queue = new jasmine.Queue(env); self.parentSuite = parentSuite; self.env = env; self.before_ = []; self.after_ = []; self.children_ = []; self.suites_ = []; self.specs_ = []; }; jasmine.Suite.prototype.getFullName = function() { var fullName = this.description; for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { fullName = parentSuite.description + ' ' + fullName; } return fullName; }; jasmine.Suite.prototype.finish = function(onComplete) { this.env.reporter.reportSuiteResults(this); this.finished = true; if (typeof(onComplete) == 'function') { onComplete(); } }; jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) { beforeEachFunction.typeName = 'beforeEach'; this.before_.unshift(beforeEachFunction); }; jasmine.Suite.prototype.afterEach = function(afterEachFunction) { afterEachFunction.typeName = 'afterEach'; this.after_.unshift(afterEachFunction); }; jasmine.Suite.prototype.results = function() { return this.queue.results(); }; jasmine.Suite.prototype.add = function(suiteOrSpec) { this.children_.push(suiteOrSpec); if (suiteOrSpec instanceof jasmine.Suite) { this.suites_.push(suiteOrSpec); this.env.currentRunner().addSuite(suiteOrSpec); } else { this.specs_.push(suiteOrSpec); } this.queue.add(suiteOrSpec); }; jasmine.Suite.prototype.specs = function() { return this.specs_; }; jasmine.Suite.prototype.suites = function() { return this.suites_; }; jasmine.Suite.prototype.children = function() { return this.children_; }; jasmine.Suite.prototype.execute = function(onComplete) { var self = this; this.queue.start(function () { self.finish(onComplete); }); }; jasmine.WaitsBlock = function(env, timeout, spec) { this.timeout = timeout; jasmine.Block.call(this, env, null, spec); }; jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block); jasmine.WaitsBlock.prototype.execute = function (onComplete) { if (jasmine.VERBOSE) { this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...'); } this.env.setTimeout(function () { onComplete(); }, this.timeout); }; /** * A block which waits for some condition to become true, with timeout. * * @constructor * @extends jasmine.Block * @param {jasmine.Env} env The Jasmine environment. * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true. * @param {Function} latchFunction A function which returns true when the desired condition has been met. * @param {String} message The message to display if the desired condition hasn't been met within the given time period. * @param {jasmine.Spec} spec The Jasmine spec. */ jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) { this.timeout = timeout || env.defaultTimeoutInterval; this.latchFunction = latchFunction; this.message = message; this.totalTimeSpentWaitingForLatch = 0; jasmine.Block.call(this, env, null, spec); }; jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block); jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10; jasmine.WaitsForBlock.prototype.execute = function(onComplete) { if (jasmine.VERBOSE) { this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen')); } var latchFunctionResult; try { latchFunctionResult = this.latchFunction.apply(this.spec); } catch (e) { this.spec.fail(e); onComplete(); return; } if (latchFunctionResult) { onComplete(); } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) { var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen'); this.spec.fail({ name: 'timeout', message: message }); this.abort = true; onComplete(); } else { this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT; var self = this; this.env.setTimeout(function() { self.execute(onComplete); }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT); } }; jasmine.version_= { "major": 1, "minor": 2, "build": 0, "revision": 1333557965, "release_candidate": 3 }; modestmaps-js-3.3.6/test/spec/000077500000000000000000000000001210227260500162175ustar00rootroot00000000000000modestmaps-js-3.3.6/test/spec/Coordinate.js000066400000000000000000000032361210227260500206500ustar00rootroot00000000000000describe('Coordinate', function() { var coordinate; beforeEach(function() { coordinate = new MM.Coordinate(0, 0, 2); }); it('provides a nice string', function() { expect(coordinate.toString()).toEqual('(0.000, 0.000 @2.000)'); }); it('generates a key', function() { expect(typeof coordinate.toKey()).toEqual('string'); }); it('can be copied', function() { expect(coordinate.copy()).toEqual(coordinate); }); it('can give its container', function() { var a = new MM.Coordinate(0.1, 0.1, 0); var b = a.container(); expect(b.column).toEqual(0); expect(b.row).toEqual(0); }); it('can be zoomed to a new zoom level', function() { var a = new MM.Coordinate(0, 0, 2); expect(a.zoom).toEqual(2); var b = a.zoomTo(4); expect(a.zoom).toEqual(2); expect(b.zoom).toEqual(4); }); it('can provide a zoomed-in coordinate', function() { expect((coordinate.zoomBy(1)).zoom).toEqual(3); }); it('can provide a zoomed-out coordinate', function() { expect((coordinate.zoomBy(-1)).zoom).toEqual(1); }); it('can move up, left, right, and down', function() { var a = new MM.Coordinate(0, 0, 2); expect(a.column).toEqual(0); var b = a.right(); expect(b.column).toEqual(1); expect(b.row).toEqual(0); var c = b.down(); expect(c.row).toEqual(1); var d = c.up(); expect(d.row).toEqual(0); var e = d.left(); expect(e.column).toEqual(0); }); it('will yield a container', function() { var oc = coordinate.copy(); coordinate.right(0.1); expect(coordinate.container().column).toEqual(oc.column); }); }); modestmaps-js-3.3.6/test/spec/DoubleClickHandler.js000066400000000000000000000022061210227260500222330ustar00rootroot00000000000000describe('DoubleClickHandler', function() { var map; beforeEach(function() { div = document.createElement('div'); div.id = +new Date(); div.style.width = 500; div.style.height = 500; var template = 'http://{S}tile.openstreetmap.org/{Z}/{X}/{Y}.png'; var subdomains = [ '', 'a.', 'b.', 'c.' ]; var provider = new MM.TemplatedLayer(template, subdomains); map = new MM.Map(div, provider, [ new MM.DoubleClickHandler() ]); map.setCenterZoom(new MM.Location(0, 0), 0); }); it('does not zoom in on single click', function() { expect(map.getZoom()).toEqual(0); happen.click(map.parent); expect(map.getZoom()).toEqual(0); }); it('zooms in on double click', function() { expect(map.getZoom()).toEqual(0); happen.dblclick(map.parent); expect(map.getZoom()).toEqual(1); }); it('zooms out on double click with shift', function() { map.setZoom(1); happen.dblclick(map.parent, { shift: true }); expect(map.getZoom()).toEqual(0); }); }); modestmaps-js-3.3.6/test/spec/DragHandler.js000066400000000000000000000023361210227260500207340ustar00rootroot00000000000000describe('DragHandler', function() { var map; beforeEach(function() { div = document.createElement('div'); div.id = +new Date(); div.style.width = 500; div.style.height = 500; var template = 'http://{S}tile.openstreetmap.org/{Z}/{X}/{Y}.png'; var subdomains = [ '', 'a.', 'b.', 'c.' ]; var provider = new MM.TemplatedLayer(template, subdomains); map = new MM.Map(div, provider, [ new MM.DragHandler() ]); map.setCenterZoom(new MM.Location(0, 0), 0); }); it('changes the cursor style to move while moving', function() { happen.mousedown(map.parent, { clientX: 10, clientY: 10 }); expect(map.parent.style.cursor).toEqual('move'); }); it('pan the map when you do a panning motion', function() { expect(~~map.getCenter().lat).toEqual(0); expect(~~map.getCenter().lon).toEqual(0); happen.mousedown(map.parent, { clientX: 10, clientY: 10 }); happen.mousemove(document, { clientX: 30, clientY: 30 }); happen.mouseup(document, { clientX: 30, clientY: 30 }); expect(~~map.getCenter().lat).toEqual(27); expect(~~map.getCenter().lon).toEqual(-28); }); }); modestmaps-js-3.3.6/test/spec/Extent.js000066400000000000000000000021221210227260500200210ustar00rootroot00000000000000describe('Extent', function() { var ext; function Receiver() { } Receiver.prototype.receive = function() { }; beforeEach(function() { ext = new MM.Extent(-10, -10, 10, 10); }); it('properly initializes its sides', function() { expect(ext.west).toEqual(-10); expect(ext.south).toEqual(-10); expect(ext.north).toEqual(10); expect(ext.east).toEqual(10); }); it('expands to fit a location', function() { ext.encloseLocation(new MM.Location(-40, -40)); expect(ext.west).toEqual(-40); expect(ext.south).toEqual(-40); }); it('expands to fit locations', function() { ext.encloseLocations([ new MM.Location(-40, -40), new MM.Location(40, 40) ]); expect(ext.west).toEqual(-40); expect(ext.east).toEqual(40); expect(ext.south).toEqual(-40); expect(ext.north).toEqual(40); }); it('knows when it contains a location', function() { expect(ext.containsLocation(new MM.Location(0, 0))).toEqual(true); expect(ext.containsLocation(new MM.Location(0, 90))).toEqual(false); }); }); modestmaps-js-3.3.6/test/spec/Globals.js000066400000000000000000000013571210227260500201460ustar00rootroot00000000000000describe('Globals', function() { it('does not leak', function() { var globalsBefore = {}; for (var key in window) { globalsBefore[key] = true; } var div = document.createElement('div'); div.id = +new Date(); div.style.width = 500; div.style.height = 500; var template = 'http://{S}tile.openstreetmap.org/{Z}/{X}/{Y}.png'; var subdomains = [ '', 'a.', 'b.', 'c.' ]; var provider = new MM.TemplatedLayer(template, subdomains); var map = new MM.Map(div, provider, new MM.Point(400, 400)); var globalsAfter = {}; for (var afterkey in window) { globalsAfter[afterkey] = true; } expect(globalsBefore).toEqual(globalsAfter); }); }); modestmaps-js-3.3.6/test/spec/Layer.js000066400000000000000000000027221210227260500176340ustar00rootroot00000000000000describe('Layer', function() { // Currently not testing subdomain-based templatedmapprovider, since // the implementation should be kind of undefined. it('layer can be created and destroyed', function() { var p = new MM.TemplatedLayer( 'http://{S}.tile.openstreetmap.org/{Z}/{X}/{Y}.png', ['a']); var l = new MM.Layer(p); l.destroy(); expect(l.map).toEqual(null); }); // Currently not testing subdomain-based templatedmapprovider, since // the implementation should be kind of undefined. it('causes the map to throw requesterror when things are not accessible', function() { var manager, message, p; var fourohfour = 'http://fffffffffffffffffffffffffffffffff.org/404.png'; runs(function() { p = new MM.TemplatedLayer(fourohfour); p.requestManager.addCallback('requesterror', function(a, b, c) { manager = a; message = b; }); var m = new MM.Map(document.createElement('div'), p, { x: 500, y: 500 }); m.setCenter({ lat: 0, lon: 0 }).setZoom(5); }); waits(500); runs(function() { expect(manager).toEqual(p.requestManager); expect(jasmine.isDomNode(message.element)).toBeTruthy(); expect(message.url).toEqual(fourohfour); expect(message.url).toEqual('http://fffffffffffffffffffffffffffffffff.org/404.png'); }); }); }); modestmaps-js-3.3.6/test/spec/Location.js000066400000000000000000000024331210227260500203270ustar00rootroot00000000000000describe('Location', function() { it('creates a location', function() { var p = new MM.Location(0, 1); expect(p.lon).toEqual(1); expect(p.lat).toEqual(0); }); it('produces a nice string', function() { var p = new MM.Location(0, 1); expect(p.toString()).toEqual('(0.000, 1.000)'); }); it('can be copied', function() { var p = new MM.Location(0, 1); expect(p.lon).toEqual(1); expect(p.lat).toEqual(0); var cp = p.copy(); expect(cp.lon).toEqual(1); expect(cp.lat).toEqual(0); }); it('can calculate distance to another location', function() { var a = new MM.Location(0, 1); var b = new MM.Location(0, 10); expect(MM.Location.distance(a, b)).toBeCloseTo(1001853.897); }); it('can interpolate a new location', function() { var a = new MM.Location(0, 1); var b = new MM.Location(0, 10); expect(MM.Location.interpolate(a, b, 0.5).lat).toBeCloseTo(0); expect(MM.Location.interpolate(a, b, 0.5).lon).toBeCloseTo(5.5); }); it('can produce a bearing between points', function() { var a = new MM.Location(0, 1); var b = new MM.Location(0, 10); expect(MM.Location.bearing(a, b)).toEqual(90); }); }); modestmaps-js-3.3.6/test/spec/Map.js000066400000000000000000000232461210227260500173010ustar00rootroot00000000000000describe('Map', function() { var map, div, sink; function Receiver() { } Receiver.prototype.receive = function() { }; beforeEach(function() { sink = new Receiver(); div = document.createElement('div'); div.id = +new Date(); div.style.width = 500; div.style.height = 500; var template = 'http://{S}tile.openstreetmap.org/{Z}/{X}/{Y}.png'; var subdomains = [ '', 'a.', 'b.', 'c.' ]; var provider = new MM.TemplatedLayer(template, subdomains); map = new MM.Map(div, provider, new MM.Point(400, 400)); map.setCenterZoom(new MM.Location(0, 0), 0); }); it('attaches itself to a parent div', function() { expect(map.parent).toEqual(div); }); it('can be initialized without a layer', function() { expect(function() { map = new MM.Map(document.createElement('div')); }).not.toThrow(); }); describe('zoom restrictions and ranges', function() { it('has set a proper zoom level', function() { expect(map.getZoom()).toEqual(0); }); it('can restrict its zoomlevel', function() { map.setZoomRange(5, 6); map.setZoom(7); expect(map.getZoom()).toEqual(6); }); it('returns itself from chainable functions', function() { expect(map.setZoomRange(5, 6)).toEqual(map); expect(map.setZoom(7)).toEqual(map); expect(map.setCenter({ lat: 5, lon: 5 })).toEqual(map); expect(map.getZoom()).toEqual(6); }); }); it('has a center coordinate', function() { expect(typeof map.coordinate.row).toEqual('number'); expect(typeof map.coordinate.column).toEqual('number'); expect(typeof map.coordinate.zoom).toEqual('number'); }); it('enforces limits when setting an extent', function() { map.dimensions = { x: 800, y: 800 }; map.zoom(2).center({ lat: 54.5259614, lon:15.2551187 }); expect(map.locationPoint({ lat: 40.7143528, lon: -74.0059731 }).y) .toBeCloseTo(384.9985102776103); }); describe('Navigation', function() { it('binds and calls drawn', function() { spyOn(sink, 'receive'); map.addCallback('drawn', sink.receive); runs(function() { map.draw(); }); waits(500); runs(function() { expect(sink.receive).toHaveBeenCalledWith(map, undefined); }); }); it('binds and calls zoomed', function() { spyOn(sink, 'receive'); map.addCallback('zoomed', sink.receive); runs(function() { map.zoomIn(); }); waits(500); runs(function() { expect(sink.receive).toHaveBeenCalledWith(map, 1); }); }); it('binds and calls panned', function() { spyOn(sink, 'receive'); map.addCallback('panned', sink.receive); runs(function() { map.panBy(2, 2); }); waits(500); runs(function() { expect(sink.receive).toHaveBeenCalledWith(map, [2, 2]); }); }); }); describe('Layer Interface', function() { it('Can set a new layer at 0', function() { var l = new MM.TemplatedLayer( 'http://{S}.tile.openstreetmap.org/{Z}/{X}/{Y}.png', ['a']); map.setLayerAt(0, l); expect(map.getLayerAt(0)).toEqual(l); }); it('Sets that layers parent to the first', function() { var l = new MM.TemplatedLayer( 'http://{S}.tile.openstreetmap.org/{Z}/{X}/{Y}.png', ['a']); map.setLayerAt(0, l); expect(map.parent.firstChild).toEqual(l.parent); }); function checkOrder() { var layers = map.getLayers(); for (var i = 0; i < layers.length; i++) { expect(map.parent.childNodes[i]).toEqual(layers[i].parent); } } it('Can insert a new layer at 1 and it will go after the first', function() { var l = new MM.TemplatedLayer( 'http://{S}.tile.openstreetmap.org/{Z}/{X}/{Y}.png', ['a']); expect(map.insertLayerAt(1, l)).toEqual(map); expect(map.getLayerAt(1)).toEqual(l); expect(map.getLayers().length).toEqual(2); checkOrder(); }); it('Can insert a new layer at 0', function() { var l = new MM.TemplatedLayer( 'http://{S}.tile.openstreetmap.org/{Z}/{X}/{Y}.png', ['a']); expect(map.insertLayerAt(0, l)).toEqual(map); expect(map.getLayerAt(0)).toEqual(l); expect(map.getLayers().length).toEqual(2); checkOrder(); }); it('Can remove a new layer at 0', function() { var l = new MM.TemplatedLayer( 'http://{S}.tile.openstreetmap.org/{Z}/{X}/{Y}.png', ['a']); expect(map.insertLayerAt(0, l)).toEqual(map); expect(map.getLayerAt(0)).toEqual(l); expect(map.getLayers().length).toEqual(2); checkOrder(); expect(map.removeLayerAt(0)).toEqual(map); expect(map.getLayers().length).toEqual(1); checkOrder(); }); it('Can swap a new layer at 0', function() { var l = new MM.TemplatedLayer( 'http://{S}.tile.openstreetmap.org/{Z}/{X}/{Y}.png', ['a']); var l1 = map.getLayerAt(0); expect(map.insertLayerAt(1, l)).toEqual(map); expect(map.swapLayersAt(0, 1)).toEqual(map); expect(map.getLayerAt(0)).toEqual(l); expect(map.getLayerAt(1)).toEqual(l1); expect(map.getLayers().length).toEqual(2); checkOrder(); }); it('Can set layers below the highest index', function() { var l = new MM.TemplatedLayer( 'http://{S}.tile.openstreetmap.org/{Z}/{X}/{Y}.png', ['a']); var l2 = new MM.TemplatedLayer( 'http://a.tile.openstreetmap.org/{Z}/{X}/{Y}.png', ['a']); expect(map.insertLayerAt(1, l)).toEqual(map); expect(map.setLayerAt(0, l2)).toEqual(map); expect(map.getLayers().length).toEqual(2); checkOrder(); }); it('Can set layers at the highest index', function() { var l = new MM.TemplatedLayer( 'http://{S}.tile.openstreetmap.org/{Z}/{X}/{Y}.png', ['a']); var l2 = new MM.TemplatedLayer( 'http://a.tile.openstreetmap.org/{Z}/{X}/{Y}.png', ['a']); expect(map.insertLayerAt(1, l)).toEqual(map); expect(map.setLayerAt(1, l2)).toEqual(map); expect(map.getLayers().length).toEqual(2); checkOrder(); }); it('Can set layers in the middle', function() { var l = new MM.TemplatedLayer( 'http://{S}.tile.openstreetmap.org/{Z}/{X}/{Y}.png', ['a']); var l2 = new MM.TemplatedLayer( 'http://a.tile.openstreetmap.org/{Z}/{X}/{Y}.png', ['a']); var l3 = new MM.TemplatedLayer( 'http://a.tile.openstreetmap.org/{Z}/{X}/{Y}.png', ['a']); expect(map.addLayer(l)).toEqual(map); expect(map.addLayer(l2)).toEqual(map); expect(map.setLayerAt(1, l3)).toEqual(map); expect(map.getLayers().length).toEqual(3); checkOrder(); }); it('Can remove a specific layer', function() { var l = new MM.TemplatedLayer( 'http://{S}.tile.openstreetmap.org/{Z}/{X}/{Y}.png', ['a']); expect(map.insertLayerAt(1, l)).toEqual(map); expect(map.removeLayer(l)).toEqual(map); expect(map.getLayers().length).toEqual(1); }); it('Can set and get a named layer', function() { var l = new MM.TemplatedLayer( 'http://{S}.tile.openstreetmap.org/{Z}/{X}/{Y}.png', ['a'], 'name'); map.addLayer(l); expect(map.getLayer('name')).toEqual(l); }); it('Can remove a named layer', function() { var l = new MM.TemplatedLayer( 'http://{S}.tile.openstreetmap.org/{Z}/{X}/{Y}.png', ['a'], 'name'); map.addLayer(l); var numLayers = map.getLayers().length; expect(map.removeLayer('name').getLayers().length).toEqual(numLayers - 1); }); it('Can disable and enable a layer by index', function() { map.disableLayerAt(0); expect(map.getLayerAt(0).enabled).toEqual(false); expect(map.getLayerAt(0).parent.style.display).toEqual('none'); map.enableLayerAt(0); expect(map.getLayerAt(0).enabled).toEqual(true); expect(map.getLayerAt(0).parent.style.display).not.toEqual('none'); }); it('Can disable and enable a layer by name', function() { var l = new MM.TemplatedLayer( 'http://{S}.tile.openstreetmap.org/{Z}/{X}/{Y}.png', ['a'], 'name'); map.addLayer(l).disableLayer('name'); expect(map.getLayer('name').enabled).toEqual(false); expect(map.getLayer('name').parent.style.display).toEqual('none'); map.enableLayerAt(1); expect(map.getLayer('name').enabled).toEqual(true); expect(map.getLayer('name').parent.style.display).not.toEqual('none'); }); }); it('can transform an extent into a coord', function() { expect(map.extentCoordinate([ { lat: -10, lon: -10 }, { lat: 10, lon: 10 }])).toEqual(new MM.Coordinate(8, 8, 4)); }); it('binds and calls resized', function() { spyOn(sink, 'receive'); map.addCallback('resized', sink.receive); runs(function() { map.setSize({ x: 200, y: 300 }); }); waits(500); runs(function() { expect(sink.receive).toHaveBeenCalledWith(map, new MM.Point(200, 300)); }); }); it('can be cleanly destroyed', function() { map.destroy(); expect(map.layers.length).toEqual(0); }); }); modestmaps-js-3.3.6/test/spec/MouseWheelHandler.js000066400000000000000000000017601210227260500221340ustar00rootroot00000000000000describe('MouseWheelHandler', function() { var map; beforeEach(function() { div = document.createElement('div'); div.id = +new Date(); div.style.width = 500; div.style.height = 500; var template = 'http://{S}tile.openstreetmap.org/{Z}/{X}/{Y}.png'; var subdomains = [ '', 'a.', 'b.', 'c.' ]; var provider = new MM.TemplatedLayer(template, subdomains); map = new MM.Map(div, provider, [ new MM.MouseWheelHandler() ]); map.setCenterZoom(new MM.Location(0, 0), 0); }); it('zooms in the map', function() { runs(function() { happen.once(map.parent, { type: 'mousewheel', detail: -100 }); }); waits(300); runs(function() { happen.once(map.parent, { type: 'mousewheel', detail: -200 }); expect(map.getZoom()).toEqual(1); }); }); }); modestmaps-js-3.3.6/test/spec/Point.js000066400000000000000000000017721210227260500176550ustar00rootroot00000000000000describe('Point', function() { it('creates a point', function() { var p = new MM.Point(0, 1); expect(p.x).toEqual(0); expect(p.y).toEqual(1); }); it('provides a nice string representation of itself', function() { var p = new MM.Point(0, 0); expect(p.toString()).toEqual('(0.000, 0.000)'); }); it('can yield a copy', function() { var p = new MM.Point(0, 0); expect(p.copy()).toEqual(p); }); it('correctly computes distance to another point', function() { var p = new MM.Point(0, 0); var q = new MM.Point(0, 10); expect(MM.Point.distance(p, q)).toEqual(10); var p1 = new MM.Point(0, 0); var q1 = new MM.Point(5, 2); expect(MM.Point.distance(p1, q1)).toBeCloseTo(5.3851); }); it('correctly interpolates positions', function() { var p = new MM.Point(0, 0); var q = new MM.Point(0, 10); expect(MM.Point.interpolate(p, q, 0.5).y).toEqual(5); }); }); modestmaps-js-3.3.6/test/spec/Projection.js000066400000000000000000000027731210227260500207020ustar00rootroot00000000000000describe('Projection', function() { var m, l; beforeEach(function() { m = new MM.MercatorProjection(10); l = new MM.LinearProjection(10); }); it('can instantiate a mercator projection', function() { // TODO: row is a very small number because of odd javascript math. expect(m.locationCoordinate(new MM.Location(0, 0)).column).toEqual(0); expect(m.locationCoordinate(new MM.Location(0, 0)).zoom).toEqual(10); expect(m.coordinateLocation(new MM.Coordinate(0, 0, 10))) .toEqual(new MM.Location(0, 0)); }); it('linear projects do not change normal points', function() { expect(l.project({x: 10, y: 10}).x).toEqual(10); expect(l.project({x: 10, y: 10}).y).toEqual(10); expect(l.unproject({x: 10, y: 10}).x).toEqual(10); expect(l.unproject({x: 10, y: 10}).y).toEqual(10); }); it('is accurate up to 3 decimals', function() { // Confirm that these values are valid up to a 3 decimals var c2 = m.locationCoordinate(new MM.Location(37, -122)); expect(Math.round(c2.row * 1000) / 1000).toEqual(0.696); expect(Math.round(c2.column * 1000) / 1000).toEqual(-2.129); expect(c2.zoom).toEqual(10); }); it('coordinatelocation to work', function() { var l2 = m.coordinateLocation(new MM.Coordinate(0.696, -2.129, 10)); expect(Math.round(l2.lat * 1000) / 1000).toEqual(37.001); expect(Math.round(l2.lon * 1000) / 1000).toEqual(-121.983); }); }); modestmaps-js-3.3.6/test/spec/Provider.js000066400000000000000000000007201210227260500203460ustar00rootroot00000000000000describe('Providers', function() { // Currently not testing subdomain-based templatedmapprovider, since // the implementation should be kind of undefined. it('basic templatedmapprovider', function() { var p = new MM.Template( 'http://{S}.tile.openstreetmap.org/{Z}/{X}/{Y}.png', ['a']); expect(p.getTile(new MM.Coordinate(1225, 1832, 12))).toEqual( 'http://a.tile.openstreetmap.org/12/1832/1225.png'); }); }); modestmaps-js-3.3.6/test/spec/Transformation.js000066400000000000000000000022011210227260500215560ustar00rootroot00000000000000describe('Transformation', function() { it('can do an identity transform', function() { var t = new MM.Transformation(1, 0, 0, 0, 1, 0); var p = new MM.Point(1, 1); var p_ = t.transform(p); var p__ = t.untransform(p_); expect(p).toEqual(new MM.Point(1, 1)); expect(p_).toEqual(new MM.Point(1, 1)); expect(p__).toEqual(new MM.Point(1, 1)); }); it('can do an inverse transform', function() { var t = new MM.Transformation(0, 1, 0, 1, 0, 0); var p = new MM.Point(0, 1); var p_ = t.transform(p); var p__ = t.untransform(p_); expect(p).toEqual(new MM.Point(0, 1)); expect(p_).toEqual(new MM.Point(1, 0)); expect(p__).toEqual(new MM.Point(0, 1)); }); it('can do an addition transform', function() { var t = new MM.Transformation(1, 0, 1, 0, 1, 1); var p = new MM.Point(0, 0); var p_ = t.transform(p); var p__ = t.untransform(p_); expect(p).toEqual(new MM.Point(0, 0)); expect(p_).toEqual(new MM.Point(1, 1)); expect(p__).toEqual(new MM.Point(0, 0)); }); }); modestmaps-js-3.3.6/test/spec/Util.js000066400000000000000000000042261210227260500174760ustar00rootroot00000000000000function Receiver() { } Receiver.prototype.receive = function() { }; describe('Util', function() { it('can extend an object', function() { function bob() {} bob.prototype.hello = function() { return 'hello world'; }; function alice() {} var a = MM.extend(alice, bob); var alice_instance = new a(); var bob_instance = new bob(); expect(alice_instance.hello()).toEqual('hello world'); expect(bob_instance.hello()).toEqual('hello world'); }); it('can get a frame', function() { sink = new Receiver(); spyOn(sink, 'receive'); runs(function() { MM.getFrame(sink.receive); }); waits(200); runs(function() { expect(sink.receive).toHaveBeenCalled(); }); }); it('can cancel events', function() { if (document.createEvent) { var d = document.createElement('div'); var evt = document.createEvent("MouseEvents"); evt.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); var evt_two = document.createEvent("MouseEvents"); evt_two.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); MM.cancelEvent(evt); expect(d.dispatchEvent(evt)).toBeFalsy(); expect(d.dispatchEvent(evt_two)).toBeTruthy(); } }); it('coerces strings into layers', function() { expect(MM.coerceLayer('http://openstreetmap.org/{Z}/{X}/{Y}.png') instanceof MM.Layer).toEqual(true); }); it('coerces providers into layers', function() { expect(MM.coerceLayer(new MM.Template('http://openstreetmap.org/{Z}/{X}/{Y}.png')) instanceof MM.Layer).toEqual(true); }); it('can get style properties', function() { var d = document.createElement('div'); d.style.display = 'block'; document.body.appendChild(d); expect(MM.getStyle(d, 'display')).toEqual('block'); document.body.removeChild(d); expect(MM.getStyle(d, 'display')).toEqual(''); }); });