0 && this._events[type].length > m) {
142 | this._events[type].warned = true;
143 | console.error('(node) warning: possible EventEmitter memory ' +
144 | 'leak detected. %d listeners added. ' +
145 | 'Use emitter.setMaxListeners() to increase limit.',
146 | this._events[type].length);
147 | if (typeof console.trace === 'function') {
148 | // not supported in IE 10
149 | console.trace();
150 | }
151 | }
152 | }
153 |
154 | return this;
155 | };
156 |
157 | EventEmitter.prototype.on = EventEmitter.prototype.addListener;
158 |
159 | EventEmitter.prototype.once = function(type, listener) {
160 | if (!isFunction(listener))
161 | throw TypeError('listener must be a function');
162 |
163 | var fired = false;
164 |
165 | function g() {
166 | this.removeListener(type, g);
167 |
168 | if (!fired) {
169 | fired = true;
170 | listener.apply(this, arguments);
171 | }
172 | }
173 |
174 | g.listener = listener;
175 | this.on(type, g);
176 |
177 | return this;
178 | };
179 |
180 | // emits a 'removeListener' event iff the listener was removed
181 | EventEmitter.prototype.removeListener = function(type, listener) {
182 | var list, position, length, i;
183 |
184 | if (!isFunction(listener))
185 | throw TypeError('listener must be a function');
186 |
187 | if (!this._events || !this._events[type])
188 | return this;
189 |
190 | list = this._events[type];
191 | length = list.length;
192 | position = -1;
193 |
194 | if (list === listener ||
195 | (isFunction(list.listener) && list.listener === listener)) {
196 | delete this._events[type];
197 | if (this._events.removeListener)
198 | this.emit('removeListener', type, listener);
199 |
200 | } else if (isObject(list)) {
201 | for (i = length; i-- > 0;) {
202 | if (list[i] === listener ||
203 | (list[i].listener && list[i].listener === listener)) {
204 | position = i;
205 | break;
206 | }
207 | }
208 |
209 | if (position < 0)
210 | return this;
211 |
212 | if (list.length === 1) {
213 | list.length = 0;
214 | delete this._events[type];
215 | } else {
216 | list.splice(position, 1);
217 | }
218 |
219 | if (this._events.removeListener)
220 | this.emit('removeListener', type, listener);
221 | }
222 |
223 | return this;
224 | };
225 |
226 | EventEmitter.prototype.removeAllListeners = function(type) {
227 | var key, listeners;
228 |
229 | if (!this._events)
230 | return this;
231 |
232 | // not listening for removeListener, no need to emit
233 | if (!this._events.removeListener) {
234 | if (arguments.length === 0)
235 | this._events = {};
236 | else if (this._events[type])
237 | delete this._events[type];
238 | return this;
239 | }
240 |
241 | // emit removeListener for all listeners on all events
242 | if (arguments.length === 0) {
243 | for (key in this._events) {
244 | if (key === 'removeListener') continue;
245 | this.removeAllListeners(key);
246 | }
247 | this.removeAllListeners('removeListener');
248 | this._events = {};
249 | return this;
250 | }
251 |
252 | listeners = this._events[type];
253 |
254 | if (isFunction(listeners)) {
255 | this.removeListener(type, listeners);
256 | } else {
257 | // LIFO order
258 | while (listeners.length)
259 | this.removeListener(type, listeners[listeners.length - 1]);
260 | }
261 | delete this._events[type];
262 |
263 | return this;
264 | };
265 |
266 | EventEmitter.prototype.listeners = function(type) {
267 | var ret;
268 | if (!this._events || !this._events[type])
269 | ret = [];
270 | else if (isFunction(this._events[type]))
271 | ret = [this._events[type]];
272 | else
273 | ret = this._events[type].slice();
274 | return ret;
275 | };
276 |
277 | EventEmitter.listenerCount = function(emitter, type) {
278 | var ret;
279 | if (!emitter._events || !emitter._events[type])
280 | ret = 0;
281 | else if (isFunction(emitter._events[type]))
282 | ret = 1;
283 | else
284 | ret = emitter._events[type].length;
285 | return ret;
286 | };
287 |
288 | function isFunction(arg) {
289 | return typeof arg === 'function';
290 | }
291 |
292 | function isNumber(arg) {
293 | return typeof arg === 'number';
294 | }
295 |
296 | function isObject(arg) {
297 | return typeof arg === 'object' && arg !== null;
298 | }
299 |
300 | function isUndefined(arg) {
301 | return arg === void 0;
302 | }
303 |
304 | },{}],2:[function(require,module,exports){
305 | var Mercator = require( './Mercator' );
306 | var utils = require( './MapUtils' );
307 | var Rect = require( './Rect' );
308 | var Tile = require( './Tile' );
309 | var events = require('events');
310 |
311 | module.exports = function(){
312 |
313 | function Map( provider, domains, width, height, minZoom, maxZoom ){
314 |
315 |
316 | //sets the scale of the map, handles retina display
317 | this.mercator = new Mercator( 256 );
318 |
319 | //events
320 | this.eventEmitter = new events.EventEmitter();
321 |
322 | //zoom bounds
323 | this._minZoom = Math.max( 0, minZoom );
324 | this._maxZoom = Math.max( 1, maxZoom );
325 |
326 | //map center
327 | this.latitude = 0;
328 | this.longitude = 0;
329 |
330 | //map zoom level
331 | this.zoom = 0;
332 |
333 | //loading
334 | this.tiles = [];
335 | this.keys = [];
336 | this.loadedTiles = [];
337 |
338 | //create domElement
339 | this.canvas = document.createElement("canvas");
340 | this.ctx = this.canvas.getContext("2d");
341 |
342 | //viewRect
343 | this.setSize( width, height );
344 |
345 | //providers
346 | this.setProvider( provider, domains, 256 * window.devicePixelRatio );
347 |
348 | //makes the math utils available
349 | this.utils = utils;
350 | }
351 |
352 | //getters / setters
353 | Map.prototype = {
354 |
355 | get width(){return this._width; }, set width( value ){this._width = value; this.setSize(this.width, this.height ); },
356 | get height(){return this._height; }, set height( value ){this._height = value; this.setSize(this.width, this.height ); },
357 | get minZoom(){return this._minZoom; }, set minZoom( value ){this._minZoom = value; },
358 | get maxZoom(){return this._maxZoom; }, set maxZoom( value ){this._maxZoom = value; }
359 |
360 | };
361 | /**
362 | * changes the Tile provider
363 | * @param provider url
364 | * @param domains sub domains
365 | * @param tileSize
366 | */
367 | function setProvider( provider, domains, tileSize )
368 | {
369 | if( this.provider == provider )return;
370 |
371 | this.dispose();
372 |
373 | this.provider = provider || "http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png";
374 | this.domains = domains || ["a","b","c"];
375 | this.scale = tileSize / 256;
376 |
377 | this.mercator = new Mercator( 256 * this.scale );
378 |
379 | this.setSize( this._width, this._height );
380 |
381 | //this.setView();
382 |
383 | }
384 | /**
385 | * sets the view rect size
386 | * @param w
387 | * @param h
388 | * @param apply boolean to apply the transform only
389 | */
390 | function setSize( w,h )
391 | {
392 | this._width = w || 256;
393 | this._height = h || 256;
394 |
395 | this.viewRect = new Rect( 0,0,this._width,this._height );
396 |
397 | if( this.canvas != null ){
398 |
399 | // set the scaled resolution
400 | this.canvas.width = this.viewRect.w * this.scale;
401 | this.canvas.height = this.viewRect.h * this.scale;
402 |
403 | // and the actual size
404 | this.canvas.style.width = ( this.viewRect.w ) + 'px';
405 | this.canvas.style.height = ( this.viewRect.h ) + 'px';
406 | }
407 |
408 | }
409 |
410 | function renderTiles() {
411 |
412 | this.ctx.clearRect( 0, 0, this.canvas.width, this.canvas.height );
413 |
414 | var c = this.getViewRectCenterPixels();
415 | var ctx = this.ctx;
416 | var zoom = this.zoom;
417 | var viewRect = this.viewRect;
418 | var scale = this.scale;
419 | this.loadedTiles.forEach(function(tile)
420 | {
421 | if( tile.zoom == zoom )
422 | {
423 | var px = viewRect.w / 2 * scale + ( tile.px - c[ 0 ] );
424 | var py = viewRect.h / 2 * scale + ( tile.py - c[ 1 ] );
425 |
426 | ctx.drawImage( tile.img, Math.round( px ), Math.round( py ) );
427 |
428 | }
429 | });
430 |
431 | this.eventEmitter.emit( Map.ON_TEXTURE_UPDATE, this.ctx );
432 |
433 | }
434 |
435 | /**
436 | * returns an array of the latitude/longitude in degrees
437 | * @returns {*[]}
438 | */
439 | function getLatLng(){
440 |
441 | return [ this.latitude, this.longitude ];
442 | }
443 |
444 | /**
445 | * returns the bounds of the view rect as an array of the latitude/longitude in degrees
446 | * @returns [ top left lat, top left lon, bottom right lat, bottom right lon]
447 | */
448 | function viewRectToLatLng( lat, lng, zoom ){
449 |
450 | var c = this.mercator.latLngToPixels( -lat, lng, zoom );
451 | var w = this.viewRect.w * this.scale;
452 | var h = this.viewRect.h * this.scale;
453 | var tl = this.mercator.pixelsToLatLng( c[ 0 ] - w / 2 * this.scale, c[ 1 ] - h / 2 * this.scale, zoom );
454 | var br = this.mercator.pixelsToLatLng( c[ 0 ] + w / 2 * this.scale, c[ 1 ] + h / 2 * this.scale, zoom );
455 | return [ -tl[ 0 ], tl[ 1 ], -br[ 0 ], br[ 1 ] ];
456 |
457 | }
458 |
459 | /**
460 | * returns the bounds of the view rect as an object of the latitude/longitude in degrees
461 | * @returns {left lon, top lat, right lon, bottom lat}
462 | */
463 | function getViewPortBounds()
464 | {
465 | var bounds = this.viewRectToLatLng( this.latitude, this.longitude, this.zoom );
466 | return{
467 | left: bounds[1],
468 | top: bounds[0],
469 | right: bounds[3],
470 | bottom: bounds[2]
471 | };
472 |
473 | }
474 |
475 | /**
476 | * converts local X & Y coordinates and zoom level to latitude and longitude
477 | * @param x X position on the canvas
478 | * @param y Y position on the canvas
479 | * @param zoom zoom level (optional, falls back to the map's current zoom level)
480 | * @returns {*} array[ lat, lon ] in degrees
481 | */
482 | function pixelsToLatLon( x,y, zoom )
483 | {
484 |
485 | var c = this.mercator.latLngToPixels( -this.latitude, this.longitude, zoom || this.zoom );
486 | var pos = map.mercator.pixelsToLatLng( c[ 0 ] - this.width / 2 + x, c[ 1 ] - this.height / 2 + y, zoom || this.zoom );
487 | pos[0] *= -1;
488 | return pos;
489 |
490 | }
491 |
492 |
493 | /**
494 | * converts latitude/longitude at a given zoom level to a local XY position
495 | * @param lat latitude in degrees
496 | * @param lon longitude in degress
497 | * @param zoom zoom level (optional, falls back to the map's current zoom level)
498 | * @returns {*} array[ X, Y ] in pixels
499 | */
500 | function latLonToPixels( lat,lon, zoom )
501 | {
502 |
503 | var c = this.mercator.latLngToPixels( -this.latitude, this.longitude, zoom || this.zoom );
504 | var p = this.mercator.latLngToPixels( -lat, lon, zoom || this.zoom );
505 | return [ (p[0]-c[0]) + this.width / 2, ( p[1] - c[1] ) + this.height / 2 ];
506 |
507 | }
508 |
509 | /**
510 | * returns the bounds of the view rect as rectangle (Rect object) of pixels in absolute coordinates
511 | * @returns new Rect( absolute x, absolute y, width, height )
512 | */
513 | function canvasPixelToLatLng( px, py, zoom ){
514 |
515 | var c = this.mercator.latLngToPixels( -ma, lng, zoom || map.zoom );
516 | return new Rect( c[ 0 ]-this.viewRect.w/2, c[ 1 ]-this.viewRect.h/2, this.viewRect.w, this.viewRect.h );
517 | }
518 |
519 | /**
520 | * returns an array of the latitude/longitude of the viewrect center in degrees
521 | * @returns {*[]}
522 | */
523 | function getViewRectCenter()
524 | {
525 | var bounds = this.viewRectToLatLng( this.latitude, this.longitude, this.zoom, this.viewRect);
526 | return [ utils.map(.5,0,1, -bounds[0], -bounds[2] ), utils.map(.5,0,1, bounds[1], bounds[3] )];
527 | }
528 |
529 | /**
530 | * returns an array of the absolute x/y coordinates of the viewrect center in pixels
531 | * @returns {*[]}
532 | */
533 | function getViewRectCenterPixels()
534 | {
535 | return this.mercator.latLngToPixels( -this.latitude, this.longitude, this.zoom );
536 | }
537 |
538 | /**
539 | * retrieves a list of tiles (loaded or not) that lie within the viewrect
540 | * @param zoom
541 | * @returns {Array}
542 | */
543 | function viewRectTiles( zoom )
544 | {
545 | zoom = zoom || this.zoom;
546 | var bounds = viewRectToLatLng( this.latitude, this.longitude, zoom, this.viewRect);
547 | var tl = this.mercator.latLonToTile(-bounds[0], bounds[1], zoom);
548 | var br = this.mercator.latLonToTile(-bounds[2], bounds[3], zoom);
549 | var u = 0;
550 | var v = 0;
551 | var tiles = [];
552 | for (var i = tl[0]; i <= br[0] ; i++)
553 | {
554 | v = 0;
555 | for (var j = tl[1]; j <= br[1]; j++)
556 | {
557 | var key = this.mercator.tileXYToQuadKey(i, j, zoom);
558 | var exists = false;
559 | for (var k = 0; k < this.loadedTiles.length; k++){
560 |
561 | if (this.loadedTiles[k].key == key ){
562 |
563 | this.loadedTiles[k].viewRectPosition[0] = u;
564 | this.loadedTiles[k].viewRectPosition[1] = v;
565 | tiles.push(this.loadedTiles[k]);
566 | exists = true;
567 | break;
568 | }
569 | }
570 | if( exists == false ) {
571 |
572 | for (k = 0; k < this.tiles.length; k++) {
573 |
574 | if (this.tiles[k].key == key) {
575 |
576 | this.tiles[k].viewRectPosition[0] = u;
577 | this.tiles[k].viewRectPosition[1] = v;
578 | tiles.push(this.tiles[k]);
579 | exists = true;
580 | break;
581 | }
582 | }
583 | }
584 | v++;
585 | }
586 | u++;
587 | }
588 | return tiles;
589 | }
590 |
591 | /**
592 | * gets a list of the tiles that are displayed in the viewrect at the given lat/lon/zoom
593 | * @param lat latitude in degrees
594 | * @param lng longitude in degrees
595 | * @param zoom zoom level
596 | * @param viewRect
597 | * @returns {Array} an array of Tile objects
598 | */
599 | function getVisibleTiles( lat, lng, zoom, viewRect )
600 | {
601 |
602 | var bounds = this.viewRectToLatLng( lat, lng, zoom, viewRect );
603 | var tl = this.mercator.latLonToTile( -bounds[0], bounds[1], zoom );
604 | var br = this.mercator.latLonToTile( -bounds[2], bounds[3], zoom );
605 |
606 | var tiles = [];
607 | for( var i = tl[ 0 ]; i <= br[ 0 ]; i++ )
608 | {
609 | for( var j = tl[ 1 ]; j <= br[ 1 ]; j++ )
610 | {
611 | var key = this.mercator.tileXYToQuadKey( i, j, zoom );
612 |
613 | //check if the tile was already loaded/being loaded
614 | if( this.keys.indexOf( key ) == -1 )
615 | {
616 | var tile = new Tile( this, key );
617 | tiles.push( tile );
618 | this.keys.push( key );
619 | }
620 | }
621 | }
622 | return tiles;
623 |
624 | }
625 |
626 | /**
627 | * sets the map view
628 | * @param lat
629 | * @param lng
630 | * @param zoom
631 | */
632 | function setView( lat, lng, zoom )
633 | {
634 | this.latitude = lat || this.latitude;
635 | this.longitude = lng || this.longitude;
636 | this.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, zoom || this.zoom ) );
637 |
638 | this.load();
639 | this.renderTiles();
640 | }
641 |
642 | /**
643 | * loads the visibles tiles
644 | */
645 | function load()
646 | {
647 |
648 | var tiles = this.getVisibleTiles( this.latitude, this.longitude, this.zoom, this.viewRect );
649 |
650 | if( tiles.length == 0 && this.tiles.length == 0 )
651 | {
652 | this.eventEmitter.emit( Map.ON_LOAD_COMPLETE, -1 );
653 | return;
654 | }
655 |
656 | for ( var i = 0; i < tiles.length; i++ ) {
657 | tiles[ i ].eventEmitter.on( Tile.ON_TILE_LOADED, this.appendTile );
658 | tiles[ i ].load();
659 | }
660 |
661 | this.tiles = this.tiles.concat( tiles );
662 |
663 | }
664 |
665 | /**
666 | * adds a loaded tile to the pool
667 | * @param tile
668 | */
669 | function appendTile( tile )
670 | {
671 |
672 | var scope = tile.map;
673 | scope.tiles.splice( scope.tiles.indexOf( tile ), 1 );
674 |
675 | var img = tile.img;
676 | scope.loadedTiles.push( tile );
677 | scope.renderTiles( true );
678 |
679 | scope.eventEmitter.emit( Map.ON_TILE_LOADED, tile );
680 |
681 | if( scope.tiles.length == 0 ){
682 |
683 | scope.eventEmitter.emit( Map.ON_LOAD_COMPLETE, 0 );
684 | }
685 | }
686 |
687 | /**
688 | * gets the map resolution in pixels at a given zoom level
689 | * @param zoom
690 | * @returns {*}
691 | */
692 | function resolution( zoom )
693 | {
694 | zoom = zoom || this.zoom;
695 | return this.mercator.resolution( zoom );
696 | }
697 |
698 | /**
699 | * destroys all the tiles
700 | */
701 | function dispose()
702 | {
703 | var scope = this;
704 | this.tiles.forEach( function(tile){ tile.eventEmitter.removeListener( Tile.ON_TILE_LOADED, scope.appendTile ); tile.valid = false; tile.dispose(); });
705 | this.loadedTiles.forEach( function(tile){ tile.dispose(); });
706 |
707 | this.tiles = [];
708 | this.loadedTiles = [];
709 | this.keys = [];
710 |
711 | }
712 |
713 | /**
714 | * returns the bounds of the view rect as rectangle (Rect object) of pixels in absolute coordinates
715 | * @returns {*[absolute x, absolute y, width, height ]}
716 | */
717 | function viewRectToPixels( lat, lng, zoom ){
718 |
719 | var c = this.mercator.latLngToPixels( -lat, lng, zoom );
720 | return new Rect( c[ 0 ], c[ 1 ], this.viewRect.w, this.viewRect.h );
721 | }
722 |
723 | //Map constants
724 | Map.ON_LOAD_COMPLETE = 0;
725 | Map.ON_TILE_LOADED = 1;
726 | Map.ON_TEXTURE_UPDATE = 2;
727 |
728 |
729 | var _p = Map.prototype;
730 | _p.constructor = Map;
731 |
732 | _p.setProvider = setProvider;
733 | _p.setSize = setSize;
734 | _p.renderTiles = renderTiles;
735 | _p.pixelsToLatLon = pixelsToLatLon;
736 | _p.latLonToPixels = latLonToPixels;
737 | _p.viewRectToLatLng = viewRectToLatLng;
738 | _p.getViewPortBounds = getViewPortBounds;
739 | _p.getViewRectCenter = getViewRectCenter;
740 | _p.getViewRectCenterPixels = getViewRectCenterPixels;
741 | _p.setView = setView;
742 | _p.viewRectTiles = viewRectTiles;
743 | _p.getVisibleTiles = getVisibleTiles;
744 | _p.load = load;
745 | _p.appendTile = appendTile;
746 | _p.resolution = resolution;
747 | _p.dispose = dispose;
748 | _p.viewRectToPixels = viewRectToPixels;
749 |
750 | return Map;
751 |
752 | }();
753 |
754 |
755 | },{"./MapUtils":3,"./Mercator":4,"./Rect":5,"./Tile":6,"events":1}],3:[function(require,module,exports){
756 |
757 | module.exports.MapUtils = function( exports )
758 | {
759 | var RAD = Math.PI / 180;
760 |
761 | function isPowerOfTwo( value ){
762 |
763 | return ( (value & -value) == value );
764 | }
765 |
766 | function powerTwo(val){
767 |
768 | if( isPowerOfTwo( val ) )return val
769 | var b = 1;
770 | while ( b < ~~( val ) )b = b << 1;
771 | return b;
772 | }
773 |
774 | //http://www.movable-type.co.uk/scripts/latlong.html
775 | function latLngDistance(lat1, lng1, lat2, lng2)
776 | {
777 | var R = EARTH_RADIUS; // km
778 | var p1 = lat1 * RAD;
779 | var p2 = lat2 * RAD;
780 | var tp = (lat2-lat1) * RAD;
781 | var td = (lng2-lng1) * RAD;
782 |
783 | var a = Math.sin(tp/2) * Math.sin(tp/2) +
784 | Math.cos(p1) * Math.cos(p2) *
785 | Math.sin(td/2) * Math.sin(td/2);
786 | var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
787 | return R * c;
788 | }
789 | function lerp ( t, a, b ){ return a + t * ( b - a ); }
790 | function norm( t, a, b ){return ( t - a ) / ( b - a );}
791 | function map( t, a0, b0, a1, b1 ){ return lerp( norm( t, a0, b0 ), a1, b1 );}
792 |
793 | // methods
794 |
795 | exports.lerp = lerp;
796 | exports.norm = norm;
797 | exports.map = map;
798 |
799 | exports.isPowerOfTwo = isPowerOfTwo;
800 | exports.powerTwo = powerTwo;
801 | exports.latLngDistance = latLngDistance;
802 |
803 | return exports;
804 |
805 | }( module.exports );
806 |
807 | },{}],4:[function(require,module,exports){
808 | /*
809 |
810 | //from http://www.maptiler.org/google-maps-coordinates-tile-bounds-projection/
811 |
812 | #!/usr/bin/env python
813 | ###############################################################################
814 | # $Id$
815 | #
816 | # Project: GDAL2Tiles, Google Summer of Code 2007 & 2008
817 | # Global Map Tiles Classes
818 | # Purpose: Convert a raster into TMS tiles, create KML SuperOverlay EPSG:4326,
819 | # generate a simple HTML viewers based on Google Maps and OpenLayers
820 | # Author: Klokan Petr Pridal, klokan at klokan dot cz
821 | # Web: http://www.klokan.cz/projects/gdal2tiles/
822 | #
823 | ###############################################################################
824 | # Copyright (c) 2008 Klokan Petr Pridal. All rights reserved.
825 | #
826 | # Permission is hereby granted, free of charge, to any person obtaining a
827 | # copy of this software and associated documentation files (the "Software"),
828 | # to deal in the Software without restriction, including without limitation
829 | # the rights to use, copy, modify, merge, publish, distribute, sublicense,
830 | # and/or sell copies of the Software, and to permit persons to whom the
831 | # Software is furnished to do so, subject to the following conditions:
832 | #
833 | # The above copyright notice and this permission notice shall be included
834 | # in all copies or substantial portions of the Software.
835 | #
836 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
837 | # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
838 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
839 | # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
840 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
841 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
842 | # DEALINGS IN THE SOFTWARE.
843 | ###############################################################################
844 |
845 | globalmaptiles.py
846 |
847 | Global Map Tiles as defined in Tile Map Service (TMS) Profiles
848 | ==============================================================
849 |
850 | Functions necessary for generation of global tiles used on the web.
851 | It contains classes implementing coordinate conversions for:
852 |
853 | - GlobalMercator (based on EPSG:900913 = EPSG:3785)
854 | for Google Maps, Yahoo Maps, Microsoft Maps compatible tiles
855 | - GlobalGeodetic (based on EPSG:4326)
856 | for OpenLayers Base Map and Google Earth compatible tiles
857 |
858 | More info at:
859 |
860 | http://wiki.osgeo.org/wiki/Tile_Map_Service_Specification
861 | http://wiki.osgeo.org/wiki/WMS_Tiling_Client_Recommendation
862 | http://msdn.microsoft.com/en-us/library/bb259689.aspx
863 | http://code.google.com/apis/maps/documentation/overlays.html#Google_Maps_Coordinates
864 |
865 | Created by Klokan Petr Pridal on 2008-07-03.
866 | Google Summer of Code 2008, project GDAL2Tiles for OSGEO.
867 |
868 | In case you use this class in your product, translate it to another language
869 | or find it usefull for your project please let me know.
870 | My email: klokan at klokan dot cz.
871 | I would like to know where it was used.
872 |
873 | Class is available under the open-source GDAL license (www.gdal.org).
874 |
875 | */
876 |
877 | module.exports = function() {
878 |
879 | function Mercator( tile_size, earth_radius )
880 | {
881 | this.init(tile_size, earth_radius);
882 | }
883 |
884 | function init(tile_size, earth_radius)
885 | {
886 | //Initialize the TMS Global Mercator pyramid
887 | this.tileSize = tile_size || 256;
888 |
889 | this.earthRadius = earth_radius || 6378137;
890 |
891 | // 156543.03392804062 for tileSize 256 pixels
892 | this.initialResolution = 2 * Math.PI * this.earthRadius / this.tileSize;
893 |
894 | // 20037508.342789244
895 | this.originShift = 2 * Math.PI * this.earthRadius / 2.0;
896 |
897 | }
898 |
899 |
900 | //converts given lat/lon in WGS84 Datum to XY in Spherical Mercator EPSG:900913
901 | function latLonToMeters( lat, lon )
902 | {
903 | var mx = lon * this.originShift / 180.0;
904 | var my = Math.log( Math.tan((90 + lat ) * Math.PI / 360.0)) / (Math.PI / 180.0);
905 | my = my * this.originShift / 180.0;
906 | return [mx, my];
907 | }
908 |
909 | //converts XY point from Spherical Mercator EPSG:900913 to lat/lon in WGS84 Datum
910 | function metersToLatLon( mx, my )
911 | {
912 | var lon = (mx / this.originShift) * 180.0;
913 | var lat = (my / this.originShift) * 180.0;
914 | lat = 180 / Math.PI * ( 2 * Math.atan( Math.exp(lat * Math.PI / 180.0 ) ) - Math.PI / 2.0);
915 | return [lat, lon];
916 | }
917 |
918 | //converts pixel coordinates in given zoom level of pyramid to EPSG:900913
919 | function pixelsToMeters( px, py, zoom )
920 | {
921 | var res = this.resolution(zoom);
922 | var mx = px * res - this.originShift;
923 | var my = py * res - this.originShift;
924 | return [mx, my];
925 | }
926 |
927 | //converts EPSG:900913 to pyramid pixel coordinates in given zoom level
928 | function metersToPixels( mx, my, zoom )
929 | {
930 | var res = this.resolution( zoom );
931 | var px = (mx + this.originShift) / res;
932 | var py = (my + this.originShift) / res;
933 | return [px, py];
934 | }
935 |
936 | //returns tile for given mercator coordinates
937 | function metersToTile( mx, my, zoom )
938 | {
939 | var pxy = this.metersToPixels(mx, my, zoom);
940 | return this.pixelsToTile(pxy[0], pxy[1]);
941 | }
942 |
943 | //returns a tile covering region in given pixel coordinates
944 | function pixelsToTile( px, py)
945 | {
946 | var tx = parseInt( Math.ceil( px / parseFloat( this.tileSize ) ) - 1);
947 | var ty = parseInt( Math.ceil( py / parseFloat( this.tileSize ) ) - 1);
948 | return [tx, ty];
949 | }
950 |
951 | //returns a tile covering region the given lat lng coordinates
952 | function latLonToTile( lat, lng, zoom )
953 | {
954 | var px = this.latLngToPixels( lat, lng, zoom );
955 | return this.pixelsToTile( px[ 0 ], px[ 1 ] );
956 | }
957 |
958 | //Move the origin of pixel coordinates to top-left corner
959 | function pixelsToRaster( px, py, zoom )
960 | {
961 | var mapSize = this.tileSize << zoom;
962 | return [px, mapSize - py];
963 | }
964 |
965 | //returns bounds of the given tile in EPSG:900913 coordinates
966 | function tileMetersBounds( tx, ty, zoom )
967 | {
968 | var min = this.pixelsToMeters(tx * this.tileSize, ty * this.tileSize, zoom);
969 | var max = this.pixelsToMeters((tx + 1) * this.tileSize, (ty + 1) * this.tileSize, zoom);
970 | return [ min[0], min[1], max[0], max[1] ];
971 | }
972 |
973 | //returns bounds of the given tile in pixels
974 | function tilePixelsBounds( tx, ty, zoom )
975 | {
976 | var bounds = this.tileMetersBounds( tx, ty, zoom );
977 | var min = this.metersToPixels(bounds[0], bounds[1], zoom );
978 | var max = this.metersToPixels(bounds[2], bounds[3], zoom );
979 | return [ min[0], min[1], max[0], max[1] ];
980 | }
981 |
982 | //returns bounds of the given tile in latutude/longitude using WGS84 datum
983 | function tileLatLngBounds( tx, ty, zoom )
984 | {
985 | var bounds = this.tileMetersBounds( tx, ty, zoom );
986 | var min = this.metersToLatLon(bounds[0], bounds[1]);
987 | var max = this.metersToLatLon(bounds[2], bounds[3]);
988 | return [ min[0], min[1], max[0], max[1] ];
989 | }
990 |
991 | //resolution (meters/pixel) for given zoom level (measured at Equator)
992 | function resolution( zoom )
993 | {
994 | return this.initialResolution / Math.pow( 2, zoom );
995 | }
996 |
997 | /**
998 | * untested...
999 | * @param pixelSize
1000 | * @returns {number}
1001 | * @constructor
1002 | */
1003 | function zoomForPixelSize( pixelSize )
1004 | {
1005 | var i = 30;
1006 | while( pixelSize > this.resolution(i) )
1007 | {
1008 | i--;
1009 | if( i <= 0 )return 0;
1010 | }
1011 | return i;
1012 | }
1013 |
1014 | //returns the lat lng of a pixel X/Y coordinates at given zoom level
1015 | function pixelsToLatLng( px, py, zoom )
1016 | {
1017 | var meters = this.pixelsToMeters( px, py, zoom );
1018 | return this.metersToLatLon( meters[ 0 ], meters[ 1 ] );
1019 | }
1020 |
1021 | //returns the pixel X/Y coordinates at given zoom level from a given lat lng
1022 | function latLngToPixels( lat, lng, zoom )
1023 | {
1024 | var meters = this.latLonToMeters( lat, lng, zoom );
1025 | return this.metersToPixels( meters[ 0 ], meters[ 1 ], zoom );
1026 | }
1027 |
1028 | //retrieves a given tile from a given lat lng
1029 | function latLngToTile( lat, lng, zoom )
1030 | {
1031 | var meters = this.latLonToMeters( lat, lng );
1032 | return this.metersToTile( meters[ 0 ], meters[ 1 ], zoom );
1033 | }
1034 |
1035 | //encodes the tlie X / Y coordinates & zoom level into a quadkey
1036 | function tileXYToQuadKey( tx,ty,zoom )
1037 | {
1038 | var quadKey = '';
1039 | for ( var i = zoom; i > 0; i-- )
1040 | {
1041 | var digit = 0;
1042 | var mask = 1 << ( i - 1 );
1043 | if( ( tx & mask ) != 0 )
1044 | {
1045 | digit++;
1046 | }
1047 | if( ( ty & mask ) != 0 )
1048 | {
1049 | digit++;
1050 | digit++;
1051 | }
1052 | quadKey += digit;
1053 | }
1054 | return quadKey;
1055 | }
1056 |
1057 | //decodes the tlie X / Y coordinates & zoom level into a quadkey
1058 | function quadKeyToTileXY( quadKeyString )
1059 | {
1060 | var tileX = 0;
1061 | var tileY = 0;
1062 | var quadKey = quadKeyString.split( '' );
1063 | var levelOfDetail = quadKey.length;
1064 |
1065 | for( var i = levelOfDetail; i > 0; i--)
1066 | {
1067 | var mask = 1 << ( i - 1 );
1068 | switch( quadKey[ levelOfDetail - i ] )
1069 | {
1070 | case '0':
1071 | break;
1072 |
1073 | case '1':
1074 | tileX |= mask;
1075 | break;
1076 |
1077 | case '2':
1078 | tileY |= mask;
1079 | break;
1080 |
1081 | case '3':
1082 | tileX |= mask;
1083 | tileY |= mask;
1084 | break;
1085 |
1086 | default:
1087 | return null;
1088 | }
1089 | }
1090 | return { tx:tileX, ty:tileY, zoom:levelOfDetail };
1091 | }
1092 |
1093 |
1094 | // public methods
1095 |
1096 | var _p = Mercator.prototype;
1097 | _p.constructor = Mercator;
1098 |
1099 | _p.init = init;
1100 | _p.latLonToMeters = latLonToMeters;
1101 | _p.metersToLatLon = metersToLatLon;
1102 | _p.pixelsToMeters = pixelsToMeters;
1103 | _p.metersToPixels = metersToPixels;
1104 | _p.metersToTile = metersToTile;
1105 | _p.pixelsToTile = pixelsToTile;
1106 | _p.latLonToTile = latLonToTile;
1107 | _p.pixelsToRaster = pixelsToRaster;
1108 | _p.tileMetersBounds = tileMetersBounds;
1109 | _p.tilePixelsBounds = tilePixelsBounds;
1110 | _p.tileLatLngBounds = tileLatLngBounds;
1111 | _p.resolution = resolution;
1112 | _p.zoomForPixelSize = zoomForPixelSize;
1113 | _p.pixelsToLatLng = pixelsToLatLng;
1114 | _p.latLngToPixels = latLngToPixels;
1115 | _p.latLngToTile = latLngToTile;
1116 | _p.tileXYToQuadKey = tileXYToQuadKey;
1117 | _p.quadKeyToTileXY = quadKeyToTileXY;
1118 |
1119 | return Mercator;
1120 |
1121 |
1122 | }();
1123 | },{}],5:[function(require,module,exports){
1124 |
1125 | module.exports = function()
1126 | {
1127 | /**
1128 | * the physical representation of the map box
1129 | * @param x
1130 | * @param y
1131 | * @param _w
1132 | * @param _h
1133 | * @constructor
1134 | */
1135 |
1136 | function Rect( x,y,w,h ) {
1137 |
1138 | this.x = x;
1139 | this.y = y;
1140 | this.w = w;
1141 | this.h = h;
1142 |
1143 | }
1144 | Rect.prototype =
1145 | {
1146 | get x(){ return this._x;}, set x( value ){ this._x = value; return this._x; },
1147 | get y(){ return this._y;}, set y( value ){ this._y = value; return this._y; },
1148 | get w(){ return this._w;}, set w( value ){ this._w = value; return this._w; },
1149 | get h(){ return this._h;}, set h( value ){ this._h = value; return this._h; }
1150 | };
1151 |
1152 | function containsPoint( _x, _y )
1153 | {
1154 | if( _x < this.x ) return false;
1155 | if( _y < this.y ) return false;
1156 | if( _x > this.x + this.w ) return false;
1157 | return _y <= this.y + this.h;
1158 | }
1159 |
1160 | function isContained( _x, _y, _w, _h )
1161 | {
1162 | return ( this.x >= _x
1163 | && this.y >= _y
1164 | && this.x + this.w <= _x + _w
1165 | && this.y + this.h <= _y + _h );
1166 | }
1167 |
1168 | function intersect( _x, _y, _w, _h )
1169 | {
1170 | return !( _x > this.x + this.w || _x+_w < this.x || _y > this.y + this.h || _y+_h< this.y );
1171 | }
1172 |
1173 | function intersection( other )
1174 | {
1175 | if( this.intersect( other.x, other.y, other.x + other.w, other.y + other.h ) )
1176 | {
1177 | var x = Math.max( this.x, other.x );
1178 | var y = Math.max( this.y, other.y );
1179 | var w = Math.min( this.x + this.w, other.x + other.w ) - x;
1180 | var h = Math.min( this.y + this.h, other.y + other.h ) - y;
1181 | return new Rect( x,y,w,h );
1182 | }
1183 | return null;
1184 | }
1185 |
1186 | var _p = Rect.prototype;
1187 |
1188 | _p.constructor = Rect;
1189 | _p.containsPoint = containsPoint;
1190 | _p.isContained = isContained;
1191 | _p.intersect = intersect;
1192 | _p.intersection = intersection;
1193 |
1194 | return Rect;
1195 |
1196 | }();
1197 | },{}],6:[function(require,module,exports){
1198 | /*
1199 | * Tile object, holds reference to:
1200 | *
1201 | * tile top left lat/lon
1202 | * tile id
1203 | * tile X/Y
1204 | * tile pixel X/Y
1205 | * the tile's DOM element
1206 | *
1207 | * + some helper methods ( contains, isContained, intersect )
1208 | *
1209 | * @param map the map this tile is bound to
1210 | * @param quadKey optional, can be set with initFromQuadKey()
1211 | * @constructor
1212 | */
1213 |
1214 | var events = require( 'events' );
1215 | module.exports = function()
1216 | {
1217 |
1218 | var undef;
1219 |
1220 | /**
1221 | * @param map Map instance this tile is associated with
1222 | * @param map the map this tile is bound to
1223 | * @param quadKey the QuadKey of this Tile
1224 | * @constructor
1225 | */
1226 | function Tile( map, quadKey )
1227 | {
1228 |
1229 | if( map == null )throw new Error( "Tile: no map associated to Tile." );
1230 | this.map = map;
1231 |
1232 | this.valid = true;
1233 | this.loaded = false;
1234 |
1235 | this.key = "";
1236 | this.id = -1;
1237 |
1238 | this.tx = -1;
1239 | this.ty = -1;
1240 | this.zoom = -1;
1241 |
1242 | this.lat = -1;
1243 | this.lng = -1;
1244 |
1245 | this.meterBounds = undef;
1246 | this.mx = -1;
1247 | this.my = -1;
1248 |
1249 | this.pixelBounds = undef;
1250 | this.px = -1;
1251 | this.py = -1;
1252 |
1253 | //raster position (tile position relative to the canvas)
1254 | this.rx = 0;
1255 | this.ry = 0;
1256 |
1257 | this.viewRectPosition = [0,0];
1258 | this.latLngBounds = undef;
1259 |
1260 | this.img = new Image();
1261 | this.url = "";
1262 |
1263 | this.eventEmitter = new events.EventEmitter();
1264 |
1265 | this.quadKey = this.key = quadKey;
1266 | if( this.quadKey != undef )
1267 | {
1268 | this.initFromQuadKey( this.quadKey );
1269 | }
1270 |
1271 | }
1272 |
1273 |
1274 |
1275 | function initFromTileXY(x, y, zoom)
1276 | {
1277 | var quadKey = this.map.mercator.tileXYToQuadKey(x, y, zoom);
1278 | initFromQuadKey(quadKey);
1279 | }
1280 |
1281 | function initFromQuadKey(quadKey)
1282 | {
1283 |
1284 | var tile = this.map.mercator.quadKeyToTileXY(quadKey);
1285 | if ( tile == undef) {
1286 | this.valid = false;
1287 | return;
1288 | }
1289 |
1290 | var center = this.map.mercator.tileLatLngBounds(this.tx + .5, this.ty + .5, this.zoom);
1291 | this.lat = -center[0];
1292 | this.lng = center[1];
1293 |
1294 | this.key = quadKey;
1295 | this.id = parseInt(quadKey);
1296 |
1297 | this.tx = tile.tx;
1298 | this.ty = tile.ty;
1299 | this.zoom = tile.zoom;
1300 |
1301 | this.meterBounds = this.map.mercator.tileMetersBounds( this.tx, this.ty, this.zoom);
1302 | this.mx = this.meterBounds[0];
1303 | this.my = this.meterBounds[1];
1304 |
1305 | this.pixelBounds = this.map.mercator.tilePixelsBounds(this.tx, this.ty, this.zoom);
1306 | this.px = this.pixelBounds[0];
1307 | this.py = this.pixelBounds[1];
1308 |
1309 | this.latLngBounds = this.map.mercator.tileLatLngBounds(this.tx, this.ty, this.zoom);
1310 | this.latLngBounds[0] *= -1;
1311 | this.latLngBounds[2] *= -1;
1312 |
1313 | this.url = this.getMapUrl(this.tx, this.ty, this.zoom);
1314 |
1315 | }
1316 |
1317 | function load( callback )
1318 | {
1319 | if( !this.valid )
1320 | {
1321 | console.log( "invalid tile, not loading");
1322 | return;
1323 | }
1324 |
1325 | var scope = this;
1326 |
1327 | this.img.tile = this;
1328 | this.img.crossOrigin = 'anonymous';
1329 | this.img.onload = function (e) {
1330 |
1331 | if( scope.map == undef )
1332 | {
1333 | console.warn( 'loaded Tile has no associated map > ', scope.key, scope.zoom );
1334 | return;
1335 | }
1336 |
1337 | if( scope.map.mercator.tileSize != e.target.width ){
1338 | scope.rescaleImage( e.target, scope.map.mercator.tileSize, window.devicePixelRatio );
1339 | }
1340 |
1341 | scope.loaded = true;
1342 | scope.eventEmitter.emit( Tile.ON_TILE_LOADED, scope );
1343 |
1344 | };
1345 | this.img.setAttribute("key", this.key);
1346 | this.img.src = this.url;
1347 | }
1348 |
1349 | /**
1350 | * rescales the image so that it fits the map's scale
1351 | * @param img input image
1352 | * @param tileSize destination tileSize
1353 | * @param scale scale factor
1354 | */
1355 | function rescaleImage( img, tileSize, scale )
1356 | {
1357 |
1358 | var canvas = document.createElement( 'canvas' );
1359 | var w = canvas.width = img.width ;
1360 | var h = canvas.height = img.height;
1361 |
1362 | //collect image data
1363 | var ctx = canvas.getContext("2d");
1364 | ctx.drawImage( img, 0,0,w,h );
1365 | var srcData = ctx.getImageData( 0,0,w,h).data;
1366 |
1367 | //result
1368 | var imgOut = ctx.createImageData(tileSize,tileSize);
1369 | var out = imgOut.data;
1370 |
1371 | //nearest neighbours upscale
1372 | for( var i = 0; i < srcData.length; i+=4 )
1373 | {
1374 | var x = ( i/4 % w ) * scale;
1375 | var y = ~~( i/4 / w ) * scale;
1376 | for( var j = x; j <= x + scale; j++ )
1377 | {
1378 | if( x >= tileSize )continue;
1379 | for( var k = y; k <= y + scale; k++ ) {
1380 |
1381 | var id = ( ~~( j ) + ~~( k ) * tileSize ) * 4;
1382 | out[id] = srcData[i ];
1383 | out[id + 1] = srcData[i+1];
1384 | out[id + 2] = srcData[i+2];
1385 | out[id + 3] = srcData[i+3];
1386 | }
1387 | }
1388 | }
1389 | canvas.width = canvas.height = tileSize;
1390 | imgOut.data = out;
1391 | ctx.putImageData(imgOut,0,0);
1392 |
1393 | //replace img with canvas
1394 | delete this.img;
1395 | this.img = canvas;
1396 |
1397 | }
1398 |
1399 | function getMapUrl(x, y, zl)
1400 | {
1401 | var url = this.map.provider;
1402 | url = url.replace(/\{x\}/, x);
1403 | url = url.replace(/\{y\}/, y);
1404 | url = url.replace(/\{z\}/, zl);
1405 |
1406 | if( url.lastIndexOf("{s}") != -1 ){
1407 | var domains = this.map.domains || [""];
1408 | url = url.replace(/\{s\}/, domains[ parseInt( Math.random() * domains.length ) ] );
1409 | }
1410 | return url;
1411 | }
1412 |
1413 | function containsLatLng(lat, lng){
1414 |
1415 | if (lat > this.latLngBounds[0]) return false;
1416 | if (lng < this.latLngBounds[1]) return false;
1417 | if (lat < this.latLngBounds[2]) return false;
1418 | if (lat > this.latLngBounds[3]) return false;
1419 | return true;
1420 | }
1421 |
1422 | function isContained(latLngBound){
1423 |
1424 | return this.latLngBounds[0] >= latLngBound[0]
1425 | && this.latLngBounds[1] >= latLngBound[1]
1426 | && this.latLngBounds[2] <= latLngBound[2]
1427 | && this.latLngBounds[3] <= latLngBound[3];
1428 | }
1429 |
1430 | function intersect(latLngBound){
1431 |
1432 | return !( latLngBound[0] < this.latLngBounds[2] ||
1433 | latLngBound[1] > this.latLngBounds[3] ||
1434 | latLngBound[2] > this.latLngBounds[0] ||
1435 | latLngBound[3] < this.latLngBounds[1] );
1436 | }
1437 |
1438 | function dispose() {
1439 |
1440 | this.map = undef;
1441 | delete this.map;
1442 | this.meterBounds = undef;
1443 | delete this.meterBounds;
1444 | this.pixelBounds = undef;
1445 | delete this.pixelBounds;
1446 |
1447 | delete this.viewRectPosition;
1448 | delete this.latLngBounds;
1449 | delete this.img;
1450 |
1451 | this.eventEmitter.removeAllListeners();
1452 | delete this.eventEmitter;
1453 |
1454 | }
1455 |
1456 | var _p = Tile.prototype;
1457 | _p.constructor = Tile;
1458 |
1459 | _p.initFromTileXY = initFromTileXY;
1460 | _p.initFromQuadKey = initFromQuadKey;
1461 | _p.load = load;
1462 | _p.rescaleImage = rescaleImage;
1463 | _p.getMapUrl = getMapUrl;
1464 | _p.containsLatLng = containsLatLng;
1465 | _p.isContained = isContained;
1466 | _p.intersect = intersect;
1467 | _p.dispose = dispose;
1468 |
1469 | Tile.ON_TILE_LOADED = 0;
1470 |
1471 | return Tile;
1472 |
1473 | }();
1474 |
1475 | },{"events":1}]},{},[2])(2)
1476 | });
--------------------------------------------------------------------------------
/example/basic.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
122 |
123 |
--------------------------------------------------------------------------------
/example/controls.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | light map utils
7 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
--------------------------------------------------------------------------------
/example/fonts/light-map.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoptere/light-map/7e34aaff9836b282ad1f3ff8acc9da76124e5773/example/fonts/light-map.eot
--------------------------------------------------------------------------------
/example/fonts/light-map.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/example/fonts/light-map.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoptere/light-map/7e34aaff9836b282ad1f3ff8acc9da76124e5773/example/fonts/light-map.ttf
--------------------------------------------------------------------------------
/example/fonts/light-map.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoptere/light-map/7e34aaff9836b282ad1f3ff8acc9da76124e5773/example/fonts/light-map.woff
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
85 |
86 |
173 |
174 |
--------------------------------------------------------------------------------
/example/light-map.min.js:
--------------------------------------------------------------------------------
1 | !function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var e;e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,e.Map=t()}}(function(){return function t(e,i,s){function n(o,h){if(!i[o]){if(!e[o]){var a="function"==typeof require&&require;if(!h&&a)return a(o,!0);if(r)return r(o,!0);var l=new Error("Cannot find module '"+o+"'");throw l.code="MODULE_NOT_FOUND",l}var u=i[o]={exports:{}};e[o][0].call(u.exports,function(t){var i=e[o][1][t];return n(i?i:t)},u,u.exports,t,e,i,s)}return i[o].exports}for(var r="function"==typeof require&&require,o=0;ot||isNaN(t))throw TypeError("n must be a positive number");return this._maxListeners=t,this},i.prototype.emit=function(t){var e,i,n,h,a,l;if(this._events||(this._events={}),"error"===t&&(!this._events.error||r(this._events.error)&&!this._events.error.length)){if(e=arguments[1],e instanceof Error)throw e;throw TypeError('Uncaught, unspecified "error" event.')}if(i=this._events[t],o(i))return!1;if(s(i))switch(arguments.length){case 1:i.call(this);break;case 2:i.call(this,arguments[1]);break;case 3:i.call(this,arguments[1],arguments[2]);break;default:for(n=arguments.length,h=new Array(n-1),a=1;n>a;a++)h[a-1]=arguments[a];i.apply(this,h)}else if(r(i)){for(n=arguments.length,h=new Array(n-1),a=1;n>a;a++)h[a-1]=arguments[a];for(l=i.slice(),n=l.length,a=0;n>a;a++)l[a].apply(this,h)}return!0},i.prototype.addListener=function(t,e){var n;if(!s(e))throw TypeError("listener must be a function");if(this._events||(this._events={}),this._events.newListener&&this.emit("newListener",t,s(e.listener)?e.listener:e),this._events[t]?r(this._events[t])?this._events[t].push(e):this._events[t]=[this._events[t],e]:this._events[t]=e,r(this._events[t])&&!this._events[t].warned){var n;n=o(this._maxListeners)?i.defaultMaxListeners:this._maxListeners,n&&n>0&&this._events[t].length>n&&(this._events[t].warned=!0,console.error("(node) warning: possible EventEmitter memory leak detected. %d listeners added. Use emitter.setMaxListeners() to increase limit.",this._events[t].length),"function"==typeof console.trace&&console.trace())}return this},i.prototype.on=i.prototype.addListener,i.prototype.once=function(t,e){function i(){this.removeListener(t,i),n||(n=!0,e.apply(this,arguments))}if(!s(e))throw TypeError("listener must be a function");var n=!1;return i.listener=e,this.on(t,i),this},i.prototype.removeListener=function(t,e){var i,n,o,h;if(!s(e))throw TypeError("listener must be a function");if(!this._events||!this._events[t])return this;if(i=this._events[t],o=i.length,n=-1,i===e||s(i.listener)&&i.listener===e)delete this._events[t],this._events.removeListener&&this.emit("removeListener",t,e);else if(r(i)){for(h=o;h-->0;)if(i[h]===e||i[h].listener&&i[h].listener===e){n=h;break}if(0>n)return this;1===i.length?(i.length=0,delete this._events[t]):i.splice(n,1),this._events.removeListener&&this.emit("removeListener",t,e)}return this},i.prototype.removeAllListeners=function(t){var e,i;if(!this._events)return this;if(!this._events.removeListener)return 0===arguments.length?this._events={}:this._events[t]&&delete this._events[t],this;if(0===arguments.length){for(e in this._events)"removeListener"!==e&&this.removeAllListeners(e);return this.removeAllListeners("removeListener"),this._events={},this}if(i=this._events[t],s(i))this.removeListener(t,i);else for(;i.length;)this.removeListener(t,i[i.length-1]);return delete this._events[t],this},i.prototype.listeners=function(t){var e;return e=this._events&&this._events[t]?s(this._events[t])?[this._events[t]]:this._events[t].slice():[]},i.listenerCount=function(t,e){var i;return i=t._events&&t._events[e]?s(t._events[e])?1:t._events[e].length:0}},{}],2:[function(t,e){var i=t("./Mercator"),s=t("./MapUtils"),n=t("./Rect"),r=t("./Tile"),o=t("events");e.exports=function(){function t(t,e,n,r,h,a){this.mercator=new i(256),this.eventEmitter=new o.EventEmitter,this._minZoom=Math.max(0,h),this._maxZoom=Math.max(1,a),this.latitude=0,this.longitude=0,this.zoom=0,this.tiles=[],this.keys=[],this.loadedTiles=[],this.canvas=document.createElement("canvas"),this.ctx=this.canvas.getContext("2d"),this.setSize(n,r),this.setProvider(t,e,256*window.devicePixelRatio),this.utils=s}function e(t,e,s){this.provider!=t&&(this.dispose(),this.provider=t||"http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",this.domains=e||["a","b","c"],this.scale=s/256,this.mercator=new i(256*this.scale),this.setSize(this._width,this._height))}function h(t,e){this._width=t||256,this._height=e||256,this.viewRect=new n(0,0,this._width,this._height),null!=this.canvas&&(this.canvas.width=this.viewRect.w*this.scale,this.canvas.height=this.viewRect.h*this.scale,this.canvas.style.width=this.viewRect.w+"px",this.canvas.style.height=this.viewRect.h+"px")}function a(){this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height);var e=this.getViewRectCenterPixels(),i=this.ctx,s=this.zoom,n=this.viewRect,r=this.scale;this.loadedTiles.forEach(function(t){if(t.zoom==s){var o=n.w/2*r+(t.px-e[0]),h=n.h/2*r+(t.py-e[1]);i.drawImage(t.img,Math.round(o),Math.round(h))}}),this.eventEmitter.emit(t.ON_TEXTURE_UPDATE,this.ctx)}function l(t,e,i){var s=this.mercator.latLngToPixels(-t,e,i),n=this.viewRect.w*this.scale,r=this.viewRect.h*this.scale,o=this.mercator.pixelsToLatLng(s[0]-n/2*this.scale,s[1]-r/2*this.scale,i),h=this.mercator.pixelsToLatLng(s[0]+n/2*this.scale,s[1]+r/2*this.scale,i);return[-o[0],o[1],-h[0],h[1]]}function u(){var t=this.viewRectToLatLng(this.latitude,this.longitude,this.zoom);return{left:t[1],top:t[0],right:t[3],bottom:t[2]}}function c(t,e,i){var s=this.mercator.latLngToPixels(-this.latitude,this.longitude,i||this.zoom),n=map.mercator.pixelsToLatLng(s[0]-this.width/2+t,s[1]-this.height/2+e,i||this.zoom);return n[0]*=-1,n}function d(t,e,i){var s=this.mercator.latLngToPixels(-this.latitude,this.longitude,i||this.zoom),n=this.mercator.latLngToPixels(-t,e,i||this.zoom);return[n[0]-s[0]+this.width/2,n[1]-s[1]+this.height/2]}function m(){var t=this.viewRectToLatLng(this.latitude,this.longitude,this.zoom,this.viewRect);return[s.map(.5,0,1,-t[0],-t[2]),s.map(.5,0,1,t[1],t[3])]}function f(){return this.mercator.latLngToPixels(-this.latitude,this.longitude,this.zoom)}function v(t){t=t||this.zoom;for(var e=l(this.latitude,this.longitude,t,this.viewRect),i=this.mercator.latLonToTile(-e[0],e[1],t),s=this.mercator.latLonToTile(-e[2],e[3],t),n=0,r=0,o=[],h=i[0];h<=s[0];h++){r=0;for(var a=i[1];a<=s[1];a++){for(var u=this.mercator.tileXYToQuadKey(h,a,t),c=!1,d=0;di;)i<<=1;return i}function s(t,e,i,s){var n=EARTH_RADIUS,r=t*h,o=i*h,a=(i-t)*h,l=(s-e)*h,u=Math.sin(a/2)*Math.sin(a/2)+Math.cos(r)*Math.cos(o)*Math.sin(l/2)*Math.sin(l/2),c=2*Math.atan2(Math.sqrt(u),Math.sqrt(1-u));return n*c}function n(t,e,i){return e+t*(i-e)}function r(t,e,i){return(t-e)/(i-e)}function o(t,e,i,s,o){return n(r(t,e,i),s,o)}var h=Math.PI/180;return t.lerp=n,t.norm=r,t.map=o,t.isPowerOfTwo=e,t.powerTwo=i,t.latLngDistance=s,t}(e.exports)},{}],4:[function(t,e){e.exports=function(){function t(t,e){this.init(t,e)}function e(t,e){this.tileSize=t||256,this.earthRadius=e||6378137,this.initialResolution=2*Math.PI*this.earthRadius/this.tileSize,this.originShift=2*Math.PI*this.earthRadius/2}function i(t,e){var i=e*this.originShift/180,s=Math.log(Math.tan((90+t)*Math.PI/360))/(Math.PI/180);return s=s*this.originShift/180,[i,s]}function s(t,e){var i=t/this.originShift*180,s=e/this.originShift*180;return s=180/Math.PI*(2*Math.atan(Math.exp(s*Math.PI/180))-Math.PI/2),[s,i]}function n(t,e,i){var s=this.resolution(i),n=t*s-this.originShift,r=e*s-this.originShift;return[n,r]}function r(t,e,i){var s=this.resolution(i),n=(t+this.originShift)/s,r=(e+this.originShift)/s;return[n,r]}function o(t,e,i){var s=this.metersToPixels(t,e,i);return this.pixelsToTile(s[0],s[1])}function h(t,e){var i=parseInt(Math.ceil(t/parseFloat(this.tileSize))-1),s=parseInt(Math.ceil(e/parseFloat(this.tileSize))-1);return[i,s]}function a(t,e,i){var s=this.latLngToPixels(t,e,i);return this.pixelsToTile(s[0],s[1])}function l(t,e,i){var s=this.tileSize<this.resolution(e);)if(e--,0>=e)return 0;return e}function v(t,e,i){var s=this.pixelsToMeters(t,e,i);return this.metersToLatLon(s[0],s[1])}function p(t,e,i){var s=this.latLonToMeters(t,e,i);return this.metersToPixels(s[0],s[1],i)}function g(t,e,i){var s=this.latLonToMeters(t,e);return this.metersToTile(s[0],s[1],i)}function L(t,e,i){for(var s="",n=i;n>0;n--){var r=0,o=1<0;r--){var o=1<this.x+this.w?!1:e<=this.y+this.h}function i(t,e,i,s){return this.x>=t&&this.y>=e&&this.x+this.w<=t+i&&this.y+this.h<=e+s}function s(t,e,i,s){return!(t>this.x+this.w||t+ithis.y+this.h||e+s ",e.key,e.zoom):(e.map.mercator.tileSize!=i.target.width&&e.rescaleImage(i.target,e.map.mercator.tileSize,window.devicePixelRatio),e.loaded=!0,void e.eventEmitter.emit(t.ON_TILE_LOADED,e))},this.img.setAttribute("key",this.key),this.img.src=this.url}function r(t,e,i){var s=document.createElement("canvas"),n=s.width=t.width,r=s.height=t.height,o=s.getContext("2d");o.drawImage(t,0,0,n,r);for(var h=o.getImageData(0,0,n,r).data,a=o.createImageData(e,e),l=a.data,u=0;u=m;m++)if(!(c>=e))for(var f=d;d+i>=f;f++){var v=4*(~~m+~~f*e);l[v]=h[u],l[v+1]=h[u+1],l[v+2]=h[u+2],l[v+3]=h[u+3]}s.width=s.height=e,a.data=l,o.putImageData(a,0,0),delete this.img,this.img=s}function o(t,e,i){var s=this.map.provider;if(s=s.replace(/\{x\}/,t),s=s.replace(/\{y\}/,e),s=s.replace(/\{z\}/,i),-1!=s.lastIndexOf("{s}")){var n=this.map.domains||[""];s=s.replace(/\{s\}/,n[parseInt(Math.random()*n.length)])}return s}function h(t,e){return t>this.latLngBounds[0]?!1:ethis.latLngBounds[3]?!1:!0}function a(t){return this.latLngBounds[0]>=t[0]&&this.latLngBounds[1]>=t[1]&&this.latLngBounds[2]<=t[2]&&this.latLngBounds[3]<=t[3]}function l(t){return!(t[0]this.latLngBounds[3]||t[2]>this.latLngBounds[0]||t[3]
--------------------------------------------------------------------------------
/example/retina.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | light map utils
6 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
176 |
177 |
178 |
179 |
--------------------------------------------------------------------------------
/example/style.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'light-map';
3 | src:url('fonts/light-map.eot?-ezg7ro');
4 | src:url('fonts/light-map.eot?#iefix-ezg7ro') format('embedded-opentype'),
5 | url('fonts/light-map.ttf?-ezg7ro') format('truetype'),
6 | url('fonts/light-map.woff?-ezg7ro') format('woff'),
7 | url('fonts/light-map.svg?-ezg7ro#light-map') format('svg');
8 | font-weight: normal;
9 | font-style: normal;
10 | }
11 |
12 | [class^="light-map-"], [class*=" light-map-"] {
13 | font-family: 'light-map';
14 | speak: none;
15 | font-style: normal;
16 | font-weight: normal;
17 | font-variant: normal;
18 | text-transform: none;
19 | line-height: 1;
20 |
21 | /* Better Font Rendering =========== */
22 | -webkit-font-smoothing: antialiased;
23 | -moz-osx-font-smoothing: grayscale;
24 | }
25 |
26 | .light-map-location:before {
27 | content: "\e600";
28 | }
29 | .light-map-plus:before {
30 | content: "\e601";
31 | }
32 | .light-map-minus:before {
33 | content: "\e602";
34 | }
35 | .light-map-up:before {
36 | content: "\e603";
37 | }
38 | .light-map-right:before {
39 | content: "\e604";
40 | }
41 | .light-map-down:before {
42 | content: "\e605";
43 | }
44 | .light-map-left:before {
45 | content: "\e606";
46 | }
47 | .light-map-circle-up:before {
48 | content: "\e607";
49 | }
50 | .light-map-circle-right:before {
51 | content: "\e608";
52 | }
53 | .light-map-circle-down:before {
54 | content: "\e609";
55 | }
56 | .light-map-circle-left:before {
57 | content: "\e60a";
58 | }
59 |
--------------------------------------------------------------------------------
/img/light-map.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoptere/light-map/7e34aaff9836b282ad1f3ff8acc9da76124e5773/img/light-map.jpg
--------------------------------------------------------------------------------
/img/retina.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nicoptere/light-map/7e34aaff9836b282ad1f3ff8acc9da76124e5773/img/retina.jpg
--------------------------------------------------------------------------------
/map/Controls.js:
--------------------------------------------------------------------------------
1 | module.exports = function()
2 | {
3 | function Controls( map ){
4 | this.map = map;
5 | this.mercator = map.mercator;
6 | }
7 |
8 |
9 | var _p = Controls.prototype;
10 |
11 |
12 | return Controls;
13 |
14 | }();
15 |
16 |
--------------------------------------------------------------------------------
/map/Map.js:
--------------------------------------------------------------------------------
1 | var Mercator = require( './Mercator' );
2 | var utils = require( './MapUtils' );
3 | var Rect = require( './Rect' );
4 | var Tile = require( './Tile' );
5 | var events = require('events');
6 |
7 | module.exports = function(){
8 |
9 | function Map( provider, domains, width, height, minZoom, maxZoom ){
10 |
11 |
12 | //sets the scale of the map, handles retina display
13 | this.mercator = new Mercator( 256 );
14 |
15 | //events
16 | this.eventEmitter = new events.EventEmitter();
17 |
18 | //zoom bounds
19 | this._minZoom = Math.max( 0, minZoom );
20 | this._maxZoom = Math.max( 1, maxZoom );
21 |
22 | //map center
23 | this.latitude = 0;
24 | this.longitude = 0;
25 |
26 | //map zoom level
27 | this.zoom = 0;
28 |
29 | //loading
30 | this.tiles = [];
31 | this.keys = [];
32 | this.loadedTiles = [];
33 |
34 | //create domElement
35 | this.canvas = document.createElement("canvas");
36 | this.ctx = this.canvas.getContext("2d");
37 |
38 | //viewRect
39 | this.setSize( width, height );
40 |
41 | //providers
42 | this.setProvider( provider, domains, 256 * window.devicePixelRatio );
43 |
44 | //makes the math utils available
45 | this.utils = utils;
46 | }
47 |
48 | //getters / setters
49 | Map.prototype = {
50 |
51 | get width(){return this._width; }, set width( value ){this._width = value; this.setSize(this.width, this.height ); },
52 | get height(){return this._height; }, set height( value ){this._height = value; this.setSize(this.width, this.height ); },
53 | get minZoom(){return this._minZoom; }, set minZoom( value ){this._minZoom = value; },
54 | get maxZoom(){return this._maxZoom; }, set maxZoom( value ){this._maxZoom = value; }
55 |
56 | };
57 | /**
58 | * changes the Tile provider
59 | * @param provider url
60 | * @param domains sub domains
61 | * @param tileSize
62 | */
63 | function setProvider( provider, domains, tileSize )
64 | {
65 | if( this.provider == provider )return;
66 |
67 | this.dispose();
68 |
69 | this.provider = provider || "http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png";
70 | this.domains = domains || ["a","b","c"];
71 | this.scale = tileSize / 256;
72 |
73 | this.mercator = new Mercator( 256 * this.scale );
74 |
75 | this.setSize( this._width, this._height );
76 |
77 | //this.setView();
78 |
79 | }
80 | /**
81 | * sets the view rect size
82 | * @param w
83 | * @param h
84 | * @param apply boolean to apply the transform only
85 | */
86 | function setSize( w,h )
87 | {
88 | this._width = w || 256;
89 | this._height = h || 256;
90 |
91 | this.viewRect = new Rect( 0,0,this._width,this._height );
92 |
93 | if( this.canvas != null ){
94 |
95 | // set the scaled resolution
96 | this.canvas.width = this.viewRect.w * this.scale;
97 | this.canvas.height = this.viewRect.h * this.scale;
98 |
99 | // and the actual size
100 | this.canvas.style.width = ( this.viewRect.w ) + 'px';
101 | this.canvas.style.height = ( this.viewRect.h ) + 'px';
102 | }
103 |
104 | }
105 |
106 | function renderTiles() {
107 |
108 | this.ctx.clearRect( 0, 0, this.canvas.width, this.canvas.height );
109 |
110 | var c = this.getViewRectCenterPixels();
111 | var ctx = this.ctx;
112 | var zoom = this.zoom;
113 | var viewRect = this.viewRect;
114 | var scale = this.scale;
115 | this.loadedTiles.forEach(function(tile)
116 | {
117 | if( tile.zoom == zoom )
118 | {
119 | var px = viewRect.w / 2 * scale + ( tile.px - c[ 0 ] );
120 | var py = viewRect.h / 2 * scale + ( tile.py - c[ 1 ] );
121 |
122 | ctx.drawImage( tile.img, Math.round( px ), Math.round( py ) );
123 |
124 | }
125 | });
126 |
127 | this.eventEmitter.emit( Map.ON_TEXTURE_UPDATE, this.ctx );
128 |
129 | }
130 |
131 | /**
132 | * returns an array of the latitude/longitude in degrees
133 | * @returns {*[]}
134 | */
135 | function getLatLng(){
136 |
137 | return [ this.latitude, this.longitude ];
138 | }
139 |
140 | /**
141 | * returns the bounds of the view rect as an array of the latitude/longitude in degrees
142 | * @returns [ top left lat, top left lon, bottom right lat, bottom right lon]
143 | */
144 | function viewRectToLatLng( lat, lng, zoom ){
145 |
146 | var c = this.mercator.latLngToPixels( -lat, lng, zoom );
147 | var w = this.viewRect.w * this.scale;
148 | var h = this.viewRect.h * this.scale;
149 | var tl = this.mercator.pixelsToLatLng( c[ 0 ] - w / 2 * this.scale, c[ 1 ] - h / 2 * this.scale, zoom );
150 | var br = this.mercator.pixelsToLatLng( c[ 0 ] + w / 2 * this.scale, c[ 1 ] + h / 2 * this.scale, zoom );
151 | return [ -tl[ 0 ], tl[ 1 ], -br[ 0 ], br[ 1 ] ];
152 |
153 | }
154 |
155 | /**
156 | * returns the bounds of the view rect as an object of the latitude/longitude in degrees
157 | * @returns {left lon, top lat, right lon, bottom lat}
158 | */
159 | function getViewPortBounds()
160 | {
161 | var bounds = this.viewRectToLatLng( this.latitude, this.longitude, this.zoom );
162 | return{
163 | left: bounds[1],
164 | top: bounds[0],
165 | right: bounds[3],
166 | bottom: bounds[2]
167 | };
168 |
169 | }
170 |
171 | /**
172 | * converts local X & Y coordinates and zoom level to latitude and longitude
173 | * @param x X position on the canvas
174 | * @param y Y position on the canvas
175 | * @param zoom zoom level (optional, falls back to the map's current zoom level)
176 | * @returns {*} array[ lat, lon ] in degrees
177 | */
178 | function pixelsToLatLon( x,y, zoom )
179 | {
180 |
181 | var c = this.mercator.latLngToPixels( -this.latitude, this.longitude, zoom || this.zoom );
182 | var pos = map.mercator.pixelsToLatLng( c[ 0 ] - this.width / 2 + x, c[ 1 ] - this.height / 2 + y, zoom || this.zoom );
183 | pos[0] *= -1;
184 | return pos;
185 |
186 | }
187 |
188 |
189 | /**
190 | * converts latitude/longitude at a given zoom level to a local XY position
191 | * @param lat latitude in degrees
192 | * @param lon longitude in degress
193 | * @param zoom zoom level (optional, falls back to the map's current zoom level)
194 | * @returns {*} array[ X, Y ] in pixels
195 | */
196 | function latLonToPixels( lat,lon, zoom )
197 | {
198 |
199 | var c = this.mercator.latLngToPixels( -this.latitude, this.longitude, zoom || this.zoom );
200 | var p = this.mercator.latLngToPixels( -lat, lon, zoom || this.zoom );
201 | return [ (p[0]-c[0]) + this.width / 2, ( p[1] - c[1] ) + this.height / 2 ];
202 |
203 | }
204 |
205 | /**
206 | * returns the bounds of the view rect as rectangle (Rect object) of pixels in absolute coordinates
207 | * @returns new Rect( absolute x, absolute y, width, height )
208 | */
209 | function canvasPixelToLatLng( px, py, zoom ){
210 |
211 | var c = this.mercator.latLngToPixels( -ma, lng, zoom || map.zoom );
212 | return new Rect( c[ 0 ]-this.viewRect.w/2, c[ 1 ]-this.viewRect.h/2, this.viewRect.w, this.viewRect.h );
213 | }
214 |
215 | /**
216 | * returns an array of the latitude/longitude of the viewrect center in degrees
217 | * @returns {*[]}
218 | */
219 | function getViewRectCenter()
220 | {
221 | var bounds = this.viewRectToLatLng( this.latitude, this.longitude, this.zoom, this.viewRect);
222 | return [ utils.map(.5,0,1, -bounds[0], -bounds[2] ), utils.map(.5,0,1, bounds[1], bounds[3] )];
223 | }
224 |
225 | /**
226 | * returns an array of the absolute x/y coordinates of the viewrect center in pixels
227 | * @returns {*[]}
228 | */
229 | function getViewRectCenterPixels()
230 | {
231 | return this.mercator.latLngToPixels( -this.latitude, this.longitude, this.zoom );
232 | }
233 |
234 | /**
235 | * retrieves a list of tiles (loaded or not) that lie within the viewrect
236 | * @param zoom
237 | * @returns {Array}
238 | */
239 | function viewRectTiles( zoom )
240 | {
241 | zoom = zoom || this.zoom;
242 | var bounds = viewRectToLatLng( this.latitude, this.longitude, zoom, this.viewRect);
243 | var tl = this.mercator.latLonToTile(-bounds[0], bounds[1], zoom);
244 | var br = this.mercator.latLonToTile(-bounds[2], bounds[3], zoom);
245 | var u = 0;
246 | var v = 0;
247 | var tiles = [];
248 | for (var i = tl[0]; i <= br[0] ; i++)
249 | {
250 | v = 0;
251 | for (var j = tl[1]; j <= br[1]; j++)
252 | {
253 | var key = this.mercator.tileXYToQuadKey(i, j, zoom);
254 | var exists = false;
255 | for (var k = 0; k < this.loadedTiles.length; k++){
256 |
257 | if (this.loadedTiles[k].key == key ){
258 |
259 | this.loadedTiles[k].viewRectPosition[0] = u;
260 | this.loadedTiles[k].viewRectPosition[1] = v;
261 | tiles.push(this.loadedTiles[k]);
262 | exists = true;
263 | break;
264 | }
265 | }
266 | if( exists == false ) {
267 |
268 | for (k = 0; k < this.tiles.length; k++) {
269 |
270 | if (this.tiles[k].key == key) {
271 |
272 | this.tiles[k].viewRectPosition[0] = u;
273 | this.tiles[k].viewRectPosition[1] = v;
274 | tiles.push(this.tiles[k]);
275 | exists = true;
276 | break;
277 | }
278 | }
279 | }
280 | v++;
281 | }
282 | u++;
283 | }
284 | return tiles;
285 | }
286 |
287 | /**
288 | * gets a list of the tiles that are displayed in the viewrect at the given lat/lon/zoom
289 | * @param lat latitude in degrees
290 | * @param lng longitude in degrees
291 | * @param zoom zoom level
292 | * @param viewRect
293 | * @returns {Array} an array of Tile objects
294 | */
295 | function getVisibleTiles( lat, lng, zoom, viewRect )
296 | {
297 |
298 | var bounds = this.viewRectToLatLng( lat, lng, zoom, viewRect );
299 | var tl = this.mercator.latLonToTile( -bounds[0], bounds[1], zoom );
300 | var br = this.mercator.latLonToTile( -bounds[2], bounds[3], zoom );
301 |
302 | var tiles = [];
303 | for( var i = tl[ 0 ]; i <= br[ 0 ]; i++ )
304 | {
305 | for( var j = tl[ 1 ]; j <= br[ 1 ]; j++ )
306 | {
307 | var key = this.mercator.tileXYToQuadKey( i, j, zoom );
308 |
309 | //check if the tile was already loaded/being loaded
310 | if( this.keys.indexOf( key ) == -1 )
311 | {
312 | var tile = new Tile( this, key );
313 | tiles.push( tile );
314 | this.keys.push( key );
315 | }
316 | }
317 | }
318 | return tiles;
319 |
320 | }
321 |
322 | /**
323 | * sets the map view
324 | * @param lat
325 | * @param lng
326 | * @param zoom
327 | */
328 | function setView( lat, lng, zoom )
329 | {
330 | this.latitude = lat || this.latitude;
331 | this.longitude = lng || this.longitude;
332 | this.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, zoom || this.zoom ) );
333 |
334 | this.load();
335 | this.renderTiles();
336 | }
337 |
338 | /**
339 | * loads the visibles tiles
340 | */
341 | function load()
342 | {
343 |
344 | var tiles = this.getVisibleTiles( this.latitude, this.longitude, this.zoom, this.viewRect );
345 |
346 | if( tiles.length == 0 && this.tiles.length == 0 )
347 | {
348 | this.eventEmitter.emit( Map.ON_LOAD_COMPLETE, -1 );
349 | return;
350 | }
351 |
352 | for ( var i = 0; i < tiles.length; i++ ) {
353 | tiles[ i ].eventEmitter.on( Tile.ON_TILE_LOADED, this.appendTile );
354 | tiles[ i ].load();
355 | }
356 |
357 | this.tiles = this.tiles.concat( tiles );
358 |
359 | }
360 |
361 | /**
362 | * adds a loaded tile to the pool
363 | * @param tile
364 | */
365 | function appendTile( tile )
366 | {
367 |
368 | var scope = tile.map;
369 | scope.tiles.splice( scope.tiles.indexOf( tile ), 1 );
370 |
371 | var img = tile.img;
372 | scope.loadedTiles.push( tile );
373 | scope.renderTiles( true );
374 |
375 | scope.eventEmitter.emit( Map.ON_TILE_LOADED, tile );
376 |
377 | if( scope.tiles.length == 0 ){
378 |
379 | scope.eventEmitter.emit( Map.ON_LOAD_COMPLETE, 0 );
380 | }
381 | }
382 |
383 | /**
384 | * gets the map resolution in pixels at a given zoom level
385 | * @param zoom
386 | * @returns {*}
387 | */
388 | function resolution( zoom )
389 | {
390 | zoom = zoom || this.zoom;
391 | return this.mercator.resolution( zoom );
392 | }
393 |
394 | /**
395 | * destroys all the tiles
396 | */
397 | function dispose()
398 | {
399 | var scope = this;
400 | this.tiles.forEach( function(tile){ tile.eventEmitter.removeListener( Tile.ON_TILE_LOADED, scope.appendTile ); tile.valid = false; tile.dispose(); });
401 | this.loadedTiles.forEach( function(tile){ tile.dispose(); });
402 |
403 | this.tiles = [];
404 | this.loadedTiles = [];
405 | this.keys = [];
406 |
407 | }
408 |
409 | /**
410 | * returns the bounds of the view rect as rectangle (Rect object) of pixels in absolute coordinates
411 | * @returns {*[absolute x, absolute y, width, height ]}
412 | */
413 | function viewRectToPixels( lat, lng, zoom ){
414 |
415 | var c = this.mercator.latLngToPixels( -lat, lng, zoom );
416 | return new Rect( c[ 0 ], c[ 1 ], this.viewRect.w, this.viewRect.h );
417 | }
418 |
419 | //Map constants
420 | Map.ON_LOAD_COMPLETE = 0;
421 | Map.ON_TILE_LOADED = 1;
422 | Map.ON_TEXTURE_UPDATE = 2;
423 |
424 |
425 | var _p = Map.prototype;
426 | _p.constructor = Map;
427 |
428 | _p.setProvider = setProvider;
429 | _p.setSize = setSize;
430 | _p.renderTiles = renderTiles;
431 | _p.pixelsToLatLon = pixelsToLatLon;
432 | _p.latLonToPixels = latLonToPixels;
433 | _p.viewRectToLatLng = viewRectToLatLng;
434 | _p.getViewPortBounds = getViewPortBounds;
435 | _p.getViewRectCenter = getViewRectCenter;
436 | _p.getViewRectCenterPixels = getViewRectCenterPixels;
437 | _p.setView = setView;
438 | _p.viewRectTiles = viewRectTiles;
439 | _p.getVisibleTiles = getVisibleTiles;
440 | _p.load = load;
441 | _p.appendTile = appendTile;
442 | _p.resolution = resolution;
443 | _p.dispose = dispose;
444 | _p.viewRectToPixels = viewRectToPixels;
445 |
446 | return Map;
447 |
448 | }();
449 |
450 |
--------------------------------------------------------------------------------
/map/MapUtils.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports.MapUtils = function( exports )
3 | {
4 | var RAD = Math.PI / 180;
5 |
6 | function isPowerOfTwo( value ){
7 |
8 | return ( (value & -value) == value );
9 | }
10 |
11 | function powerTwo(val){
12 |
13 | if( isPowerOfTwo( val ) )return val
14 | var b = 1;
15 | while ( b < ~~( val ) )b = b << 1;
16 | return b;
17 | }
18 |
19 | //http://www.movable-type.co.uk/scripts/latlong.html
20 | function latLngDistance(lat1, lng1, lat2, lng2)
21 | {
22 | var R = EARTH_RADIUS; // km
23 | var p1 = lat1 * RAD;
24 | var p2 = lat2 * RAD;
25 | var tp = (lat2-lat1) * RAD;
26 | var td = (lng2-lng1) * RAD;
27 |
28 | var a = Math.sin(tp/2) * Math.sin(tp/2) +
29 | Math.cos(p1) * Math.cos(p2) *
30 | Math.sin(td/2) * Math.sin(td/2);
31 | var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
32 | return R * c;
33 | }
34 | function lerp ( t, a, b ){ return a + t * ( b - a ); }
35 | function norm( t, a, b ){return ( t - a ) / ( b - a );}
36 | function map( t, a0, b0, a1, b1 ){ return lerp( norm( t, a0, b0 ), a1, b1 );}
37 |
38 | // methods
39 |
40 | exports.lerp = lerp;
41 | exports.norm = norm;
42 | exports.map = map;
43 |
44 | exports.isPowerOfTwo = isPowerOfTwo;
45 | exports.powerTwo = powerTwo;
46 | exports.latLngDistance = latLngDistance;
47 |
48 | return exports;
49 |
50 | }( module.exports );
51 |
--------------------------------------------------------------------------------
/map/Rect.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = function()
3 | {
4 | /**
5 | * the physical representation of the map box
6 | * @param x
7 | * @param y
8 | * @param _w
9 | * @param _h
10 | * @constructor
11 | */
12 |
13 | function Rect( x,y,w,h ) {
14 |
15 | this.x = x;
16 | this.y = y;
17 | this.w = w;
18 | this.h = h;
19 |
20 | }
21 | Rect.prototype =
22 | {
23 | get x(){ return this._x;}, set x( value ){ this._x = value; return this._x; },
24 | get y(){ return this._y;}, set y( value ){ this._y = value; return this._y; },
25 | get w(){ return this._w;}, set w( value ){ this._w = value; return this._w; },
26 | get h(){ return this._h;}, set h( value ){ this._h = value; return this._h; }
27 | };
28 |
29 | function containsPoint( _x, _y )
30 | {
31 | if( _x < this.x ) return false;
32 | if( _y < this.y ) return false;
33 | if( _x > this.x + this.w ) return false;
34 | return _y <= this.y + this.h;
35 | }
36 |
37 | function isContained( _x, _y, _w, _h )
38 | {
39 | return ( this.x >= _x
40 | && this.y >= _y
41 | && this.x + this.w <= _x + _w
42 | && this.y + this.h <= _y + _h );
43 | }
44 |
45 | function intersect( _x, _y, _w, _h )
46 | {
47 | return !( _x > this.x + this.w || _x+_w < this.x || _y > this.y + this.h || _y+_h< this.y );
48 | }
49 |
50 | function intersection( other )
51 | {
52 | if( this.intersect( other.x, other.y, other.x + other.w, other.y + other.h ) )
53 | {
54 | var x = Math.max( this.x, other.x );
55 | var y = Math.max( this.y, other.y );
56 | var w = Math.min( this.x + this.w, other.x + other.w ) - x;
57 | var h = Math.min( this.y + this.h, other.y + other.h ) - y;
58 | return new Rect( x,y,w,h );
59 | }
60 | return null;
61 | }
62 |
63 | var _p = Rect.prototype;
64 |
65 | _p.constructor = Rect;
66 | _p.containsPoint = containsPoint;
67 | _p.isContained = isContained;
68 | _p.intersect = intersect;
69 | _p.intersection = intersection;
70 |
71 | return Rect;
72 |
73 | }();
--------------------------------------------------------------------------------
/map/Tile.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Tile object, holds reference to:
3 | *
4 | * tile top left lat/lon
5 | * tile id
6 | * tile X/Y
7 | * tile pixel X/Y
8 | * the tile's DOM element
9 | *
10 | * + some helper methods ( contains, isContained, intersect )
11 | *
12 | * @param map the map this tile is bound to
13 | * @param quadKey optional, can be set with initFromQuadKey()
14 | * @constructor
15 | */
16 |
17 | var events = require( 'events' );
18 | module.exports = function()
19 | {
20 |
21 | var undef;
22 |
23 | /**
24 | * @param map Map instance this tile is associated with
25 | * @param map the map this tile is bound to
26 | * @param quadKey the QuadKey of this Tile
27 | * @constructor
28 | */
29 | function Tile( map, quadKey )
30 | {
31 |
32 | if( map == null )throw new Error( "Tile: no map associated to Tile." );
33 | this.map = map;
34 |
35 | this.valid = true;
36 | this.loaded = false;
37 |
38 | this.key = "";
39 | this.id = -1;
40 |
41 | this.tx = -1;
42 | this.ty = -1;
43 | this.zoom = -1;
44 |
45 | this.lat = -1;
46 | this.lng = -1;
47 |
48 | this.meterBounds = undef;
49 | this.mx = -1;
50 | this.my = -1;
51 |
52 | this.pixelBounds = undef;
53 | this.px = -1;
54 | this.py = -1;
55 |
56 | //raster position (tile position relative to the canvas)
57 | this.rx = 0;
58 | this.ry = 0;
59 |
60 | this.viewRectPosition = [0,0];
61 | this.latLngBounds = undef;
62 |
63 | this.img = new Image();
64 | this.url = "";
65 |
66 | this.eventEmitter = new events.EventEmitter();
67 |
68 | this.quadKey = this.key = quadKey;
69 | if( this.quadKey != undef )
70 | {
71 | this.initFromQuadKey( this.quadKey );
72 | }
73 |
74 | }
75 |
76 |
77 |
78 | function initFromTileXY(x, y, zoom)
79 | {
80 | var quadKey = this.map.mercator.tileXYToQuadKey(x, y, zoom);
81 | initFromQuadKey(quadKey);
82 | }
83 |
84 | function initFromQuadKey(quadKey)
85 | {
86 |
87 | var tile = this.map.mercator.quadKeyToTileXY(quadKey);
88 | if ( tile == undef) {
89 | this.valid = false;
90 | return;
91 | }
92 |
93 | var center = this.map.mercator.tileLatLngBounds(this.tx + .5, this.ty + .5, this.zoom);
94 | this.lat = -center[0];
95 | this.lng = center[1];
96 |
97 | this.key = quadKey;
98 | this.id = parseInt(quadKey);
99 |
100 | this.tx = tile.tx;
101 | this.ty = tile.ty;
102 | this.zoom = tile.zoom;
103 |
104 | this.meterBounds = this.map.mercator.tileMetersBounds( this.tx, this.ty, this.zoom);
105 | this.mx = this.meterBounds[0];
106 | this.my = this.meterBounds[1];
107 |
108 | this.pixelBounds = this.map.mercator.tilePixelsBounds(this.tx, this.ty, this.zoom);
109 | this.px = this.pixelBounds[0];
110 | this.py = this.pixelBounds[1];
111 |
112 | this.latLngBounds = this.map.mercator.tileLatLngBounds(this.tx, this.ty, this.zoom);
113 | this.latLngBounds[0] *= -1;
114 | this.latLngBounds[2] *= -1;
115 |
116 | this.url = this.getMapUrl(this.tx, this.ty, this.zoom);
117 |
118 | }
119 |
120 | function load( callback )
121 | {
122 | if( !this.valid )
123 | {
124 | console.log( "invalid tile, not loading");
125 | return;
126 | }
127 |
128 | var scope = this;
129 |
130 | this.img.tile = this;
131 | this.img.crossOrigin = 'anonymous';
132 | this.img.onload = function (e) {
133 |
134 | if( scope.map == undef )
135 | {
136 | console.warn( 'loaded Tile has no associated map > ', scope.key, scope.zoom );
137 | return;
138 | }
139 |
140 | if( scope.map.mercator.tileSize != e.target.width ){
141 | scope.rescaleImage( e.target, scope.map.mercator.tileSize, window.devicePixelRatio );
142 | }
143 |
144 | scope.loaded = true;
145 | scope.eventEmitter.emit( Tile.ON_TILE_LOADED, scope );
146 |
147 | };
148 | this.img.setAttribute("key", this.key);
149 | this.img.src = this.url;
150 | }
151 |
152 | /**
153 | * rescales the image so that it fits the map's scale
154 | * @param img input image
155 | * @param tileSize destination tileSize
156 | * @param scale scale factor
157 | */
158 | function rescaleImage( img, tileSize, scale )
159 | {
160 |
161 | var canvas = document.createElement( 'canvas' );
162 | var w = canvas.width = img.width ;
163 | var h = canvas.height = img.height;
164 |
165 | //collect image data
166 | var ctx = canvas.getContext("2d");
167 | ctx.drawImage( img, 0,0,w,h );
168 | var srcData = ctx.getImageData( 0,0,w,h).data;
169 |
170 | //result
171 | var imgOut = ctx.createImageData(tileSize,tileSize);
172 | var out = imgOut.data;
173 |
174 | //nearest neighbours upscale
175 | for( var i = 0; i < srcData.length; i+=4 )
176 | {
177 | var x = ( i/4 % w ) * scale;
178 | var y = ~~( i/4 / w ) * scale;
179 | for( var j = x; j <= x + scale; j++ )
180 | {
181 | if( x >= tileSize )continue;
182 | for( var k = y; k <= y + scale; k++ ) {
183 |
184 | var id = ( ~~( j ) + ~~( k ) * tileSize ) * 4;
185 | out[id] = srcData[i ];
186 | out[id + 1] = srcData[i+1];
187 | out[id + 2] = srcData[i+2];
188 | out[id + 3] = srcData[i+3];
189 | }
190 | }
191 | }
192 | canvas.width = canvas.height = tileSize;
193 | imgOut.data = out;
194 | ctx.putImageData(imgOut,0,0);
195 |
196 | //replace img with canvas
197 | delete this.img;
198 | this.img = canvas;
199 |
200 | }
201 |
202 | function getMapUrl(x, y, zl)
203 | {
204 | var url = this.map.provider;
205 | url = url.replace(/\{x\}/, x);
206 | url = url.replace(/\{y\}/, y);
207 | url = url.replace(/\{z\}/, zl);
208 |
209 | if( url.lastIndexOf("{s}") != -1 ){
210 | var domains = this.map.domains || [""];
211 | url = url.replace(/\{s\}/, domains[ parseInt( Math.random() * domains.length ) ] );
212 | }
213 | return url;
214 | }
215 |
216 | function containsLatLng(lat, lng){
217 |
218 | if (lat > this.latLngBounds[0]) return false;
219 | if (lng < this.latLngBounds[1]) return false;
220 | if (lat < this.latLngBounds[2]) return false;
221 | if (lat > this.latLngBounds[3]) return false;
222 | return true;
223 | }
224 |
225 | function isContained(latLngBound){
226 |
227 | return this.latLngBounds[0] >= latLngBound[0]
228 | && this.latLngBounds[1] >= latLngBound[1]
229 | && this.latLngBounds[2] <= latLngBound[2]
230 | && this.latLngBounds[3] <= latLngBound[3];
231 | }
232 |
233 | function intersect(latLngBound){
234 |
235 | return !( latLngBound[0] < this.latLngBounds[2] ||
236 | latLngBound[1] > this.latLngBounds[3] ||
237 | latLngBound[2] > this.latLngBounds[0] ||
238 | latLngBound[3] < this.latLngBounds[1] );
239 | }
240 |
241 | function dispose() {
242 |
243 | this.map = undef;
244 | delete this.map;
245 | this.meterBounds = undef;
246 | delete this.meterBounds;
247 | this.pixelBounds = undef;
248 | delete this.pixelBounds;
249 |
250 | delete this.viewRectPosition;
251 | delete this.latLngBounds;
252 | delete this.img;
253 |
254 | this.eventEmitter.removeAllListeners();
255 | delete this.eventEmitter;
256 |
257 | }
258 |
259 | var _p = Tile.prototype;
260 | _p.constructor = Tile;
261 |
262 | _p.initFromTileXY = initFromTileXY;
263 | _p.initFromQuadKey = initFromQuadKey;
264 | _p.load = load;
265 | _p.rescaleImage = rescaleImage;
266 | _p.getMapUrl = getMapUrl;
267 | _p.containsLatLng = containsLatLng;
268 | _p.isContained = isContained;
269 | _p.intersect = intersect;
270 | _p.dispose = dispose;
271 |
272 | Tile.ON_TILE_LOADED = 0;
273 |
274 | return Tile;
275 |
276 | }();
277 |
--------------------------------------------------------------------------------
/map/mercator.js:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | //from http://www.maptiler.org/google-maps-coordinates-tile-bounds-projection/
4 |
5 | #!/usr/bin/env python
6 | ###############################################################################
7 | # $Id$
8 | #
9 | # Project: GDAL2Tiles, Google Summer of Code 2007 & 2008
10 | # Global Map Tiles Classes
11 | # Purpose: Convert a raster into TMS tiles, create KML SuperOverlay EPSG:4326,
12 | # generate a simple HTML viewers based on Google Maps and OpenLayers
13 | # Author: Klokan Petr Pridal, klokan at klokan dot cz
14 | # Web: http://www.klokan.cz/projects/gdal2tiles/
15 | #
16 | ###############################################################################
17 | # Copyright (c) 2008 Klokan Petr Pridal. All rights reserved.
18 | #
19 | # Permission is hereby granted, free of charge, to any person obtaining a
20 | # copy of this software and associated documentation files (the "Software"),
21 | # to deal in the Software without restriction, including without limitation
22 | # the rights to use, copy, modify, merge, publish, distribute, sublicense,
23 | # and/or sell copies of the Software, and to permit persons to whom the
24 | # Software is furnished to do so, subject to the following conditions:
25 | #
26 | # The above copyright notice and this permission notice shall be included
27 | # in all copies or substantial portions of the Software.
28 | #
29 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
30 | # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
31 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
32 | # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
33 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
34 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
35 | # DEALINGS IN THE SOFTWARE.
36 | ###############################################################################
37 |
38 | globalmaptiles.py
39 |
40 | Global Map Tiles as defined in Tile Map Service (TMS) Profiles
41 | ==============================================================
42 |
43 | Functions necessary for generation of global tiles used on the web.
44 | It contains classes implementing coordinate conversions for:
45 |
46 | - GlobalMercator (based on EPSG:900913 = EPSG:3785)
47 | for Google Maps, Yahoo Maps, Microsoft Maps compatible tiles
48 | - GlobalGeodetic (based on EPSG:4326)
49 | for OpenLayers Base Map and Google Earth compatible tiles
50 |
51 | More info at:
52 |
53 | http://wiki.osgeo.org/wiki/Tile_Map_Service_Specification
54 | http://wiki.osgeo.org/wiki/WMS_Tiling_Client_Recommendation
55 | http://msdn.microsoft.com/en-us/library/bb259689.aspx
56 | http://code.google.com/apis/maps/documentation/overlays.html#Google_Maps_Coordinates
57 |
58 | Created by Klokan Petr Pridal on 2008-07-03.
59 | Google Summer of Code 2008, project GDAL2Tiles for OSGEO.
60 |
61 | In case you use this class in your product, translate it to another language
62 | or find it usefull for your project please let me know.
63 | My email: klokan at klokan dot cz.
64 | I would like to know where it was used.
65 |
66 | Class is available under the open-source GDAL license (www.gdal.org).
67 |
68 | */
69 |
70 | module.exports = function() {
71 |
72 | function Mercator( tile_size, earth_radius )
73 | {
74 | this.init(tile_size, earth_radius);
75 | }
76 |
77 | function init(tile_size, earth_radius)
78 | {
79 | //Initialize the TMS Global Mercator pyramid
80 | this.tileSize = tile_size || 256;
81 |
82 | this.earthRadius = earth_radius || 6378137;
83 |
84 | // 156543.03392804062 for tileSize 256 pixels
85 | this.initialResolution = 2 * Math.PI * this.earthRadius / this.tileSize;
86 |
87 | // 20037508.342789244
88 | this.originShift = 2 * Math.PI * this.earthRadius / 2.0;
89 |
90 | }
91 |
92 |
93 | //converts given lat/lon in WGS84 Datum to XY in Spherical Mercator EPSG:900913
94 | function latLonToMeters( lat, lon )
95 | {
96 | var mx = lon * this.originShift / 180.0;
97 | var my = Math.log( Math.tan((90 + lat ) * Math.PI / 360.0)) / (Math.PI / 180.0);
98 | my = my * this.originShift / 180.0;
99 | return [mx, my];
100 | }
101 |
102 | //converts XY point from Spherical Mercator EPSG:900913 to lat/lon in WGS84 Datum
103 | function metersToLatLon( mx, my )
104 | {
105 | var lon = (mx / this.originShift) * 180.0;
106 | var lat = (my / this.originShift) * 180.0;
107 | lat = 180 / Math.PI * ( 2 * Math.atan( Math.exp(lat * Math.PI / 180.0 ) ) - Math.PI / 2.0);
108 | return [lat, lon];
109 | }
110 |
111 | //converts pixel coordinates in given zoom level of pyramid to EPSG:900913
112 | function pixelsToMeters( px, py, zoom )
113 | {
114 | var res = this.resolution(zoom);
115 | var mx = px * res - this.originShift;
116 | var my = py * res - this.originShift;
117 | return [mx, my];
118 | }
119 |
120 | //converts EPSG:900913 to pyramid pixel coordinates in given zoom level
121 | function metersToPixels( mx, my, zoom )
122 | {
123 | var res = this.resolution( zoom );
124 | var px = (mx + this.originShift) / res;
125 | var py = (my + this.originShift) / res;
126 | return [px, py];
127 | }
128 |
129 | //returns tile for given mercator coordinates
130 | function metersToTile( mx, my, zoom )
131 | {
132 | var pxy = this.metersToPixels(mx, my, zoom);
133 | return this.pixelsToTile(pxy[0], pxy[1]);
134 | }
135 |
136 | //returns a tile covering region in given pixel coordinates
137 | function pixelsToTile( px, py)
138 | {
139 | var tx = parseInt( Math.ceil( px / parseFloat( this.tileSize ) ) - 1);
140 | var ty = parseInt( Math.ceil( py / parseFloat( this.tileSize ) ) - 1);
141 | return [tx, ty];
142 | }
143 |
144 | //returns a tile covering region the given lat lng coordinates
145 | function latLonToTile( lat, lng, zoom )
146 | {
147 | var px = this.latLngToPixels( lat, lng, zoom );
148 | return this.pixelsToTile( px[ 0 ], px[ 1 ] );
149 | }
150 |
151 | //Move the origin of pixel coordinates to top-left corner
152 | function pixelsToRaster( px, py, zoom )
153 | {
154 | var mapSize = this.tileSize << zoom;
155 | return [px, mapSize - py];
156 | }
157 |
158 | //returns bounds of the given tile in EPSG:900913 coordinates
159 | function tileMetersBounds( tx, ty, zoom )
160 | {
161 | var min = this.pixelsToMeters(tx * this.tileSize, ty * this.tileSize, zoom);
162 | var max = this.pixelsToMeters((tx + 1) * this.tileSize, (ty + 1) * this.tileSize, zoom);
163 | return [ min[0], min[1], max[0], max[1] ];
164 | }
165 |
166 | //returns bounds of the given tile in pixels
167 | function tilePixelsBounds( tx, ty, zoom )
168 | {
169 | var bounds = this.tileMetersBounds( tx, ty, zoom );
170 | var min = this.metersToPixels(bounds[0], bounds[1], zoom );
171 | var max = this.metersToPixels(bounds[2], bounds[3], zoom );
172 | return [ min[0], min[1], max[0], max[1] ];
173 | }
174 |
175 | //returns bounds of the given tile in latutude/longitude using WGS84 datum
176 | function tileLatLngBounds( tx, ty, zoom )
177 | {
178 | var bounds = this.tileMetersBounds( tx, ty, zoom );
179 | var min = this.metersToLatLon(bounds[0], bounds[1]);
180 | var max = this.metersToLatLon(bounds[2], bounds[3]);
181 | return [ min[0], min[1], max[0], max[1] ];
182 | }
183 |
184 | //resolution (meters/pixel) for given zoom level (measured at Equator)
185 | function resolution( zoom )
186 | {
187 | return this.initialResolution / Math.pow( 2, zoom );
188 | }
189 |
190 | /**
191 | * untested...
192 | * @param pixelSize
193 | * @returns {number}
194 | * @constructor
195 | */
196 | function zoomForPixelSize( pixelSize )
197 | {
198 | var i = 30;
199 | while( pixelSize > this.resolution(i) )
200 | {
201 | i--;
202 | if( i <= 0 )return 0;
203 | }
204 | return i;
205 | }
206 |
207 | //returns the lat lng of a pixel X/Y coordinates at given zoom level
208 | function pixelsToLatLng( px, py, zoom )
209 | {
210 | var meters = this.pixelsToMeters( px, py, zoom );
211 | return this.metersToLatLon( meters[ 0 ], meters[ 1 ] );
212 | }
213 |
214 | //returns the pixel X/Y coordinates at given zoom level from a given lat lng
215 | function latLngToPixels( lat, lng, zoom )
216 | {
217 | var meters = this.latLonToMeters( lat, lng, zoom );
218 | return this.metersToPixels( meters[ 0 ], meters[ 1 ], zoom );
219 | }
220 |
221 | //retrieves a given tile from a given lat lng
222 | function latLngToTile( lat, lng, zoom )
223 | {
224 | var meters = this.latLonToMeters( lat, lng );
225 | return this.metersToTile( meters[ 0 ], meters[ 1 ], zoom );
226 | }
227 |
228 | //encodes the tlie X / Y coordinates & zoom level into a quadkey
229 | function tileXYToQuadKey( tx,ty,zoom )
230 | {
231 | var quadKey = '';
232 | for ( var i = zoom; i > 0; i-- )
233 | {
234 | var digit = 0;
235 | var mask = 1 << ( i - 1 );
236 | if( ( tx & mask ) != 0 )
237 | {
238 | digit++;
239 | }
240 | if( ( ty & mask ) != 0 )
241 | {
242 | digit++;
243 | digit++;
244 | }
245 | quadKey += digit;
246 | }
247 | return quadKey;
248 | }
249 |
250 | //decodes the tlie X / Y coordinates & zoom level into a quadkey
251 | function quadKeyToTileXY( quadKeyString )
252 | {
253 | var tileX = 0;
254 | var tileY = 0;
255 | var quadKey = quadKeyString.split( '' );
256 | var levelOfDetail = quadKey.length;
257 |
258 | for( var i = levelOfDetail; i > 0; i--)
259 | {
260 | var mask = 1 << ( i - 1 );
261 | switch( quadKey[ levelOfDetail - i ] )
262 | {
263 | case '0':
264 | break;
265 |
266 | case '1':
267 | tileX |= mask;
268 | break;
269 |
270 | case '2':
271 | tileY |= mask;
272 | break;
273 |
274 | case '3':
275 | tileX |= mask;
276 | tileY |= mask;
277 | break;
278 |
279 | default:
280 | return null;
281 | }
282 | }
283 | return { tx:tileX, ty:tileY, zoom:levelOfDetail };
284 | }
285 |
286 |
287 | // public methods
288 |
289 | var _p = Mercator.prototype;
290 | _p.constructor = Mercator;
291 |
292 | _p.init = init;
293 | _p.latLonToMeters = latLonToMeters;
294 | _p.metersToLatLon = metersToLatLon;
295 | _p.pixelsToMeters = pixelsToMeters;
296 | _p.metersToPixels = metersToPixels;
297 | _p.metersToTile = metersToTile;
298 | _p.pixelsToTile = pixelsToTile;
299 | _p.latLonToTile = latLonToTile;
300 | _p.pixelsToRaster = pixelsToRaster;
301 | _p.tileMetersBounds = tileMetersBounds;
302 | _p.tilePixelsBounds = tilePixelsBounds;
303 | _p.tileLatLngBounds = tileLatLngBounds;
304 | _p.resolution = resolution;
305 | _p.zoomForPixelSize = zoomForPixelSize;
306 | _p.pixelsToLatLng = pixelsToLatLng;
307 | _p.latLngToPixels = latLngToPixels;
308 | _p.latLngToTile = latLngToTile;
309 | _p.tileXYToQuadKey = tileXYToQuadKey;
310 | _p.quadKeyToTileXY = quadKeyToTileXY;
311 |
312 | return Mercator;
313 |
314 |
315 | }();
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "light-map",
3 | "version": "0.0.6",
4 | "description": "a light-weight XYZ tile map viewer with a Canvas2D renderer",
5 | "main": "bin/light-map.js",
6 | "scripts": {
7 | "test": "new Map( provider, domains, 512,512, 0,10 )"
8 | },
9 | "keywords": [
10 | "map",
11 | "maps",
12 | "tms",
13 | "WGS84",
14 | "canvas",
15 | "self-contained",
16 | "mercator"
17 | ],
18 | "author": "Nicolas Barradeau",
19 | "maintainers": [
20 | {
21 | "name": "Nicolas Barradeau",
22 | "email": "nicoptere@gmail.com",
23 | "web": "http://www.barradeau.com/blog"
24 | }
25 | ],
26 |
27 | "dependencies" :{
28 | "events":"1.0.2"
29 | },
30 | "devDependencies": {
31 | "browserify" : "^11.0.0",
32 | "uglifyjs": "^2.4.10"
33 | },
34 |
35 | "scripts": {
36 | "debug": "browserify map/Map.js --standalone Map -o example/light-map.min.js",
37 | "bundle": "browserify map/Map.js --standalone Map -o bin/light-map.js",
38 | "uglify": "uglifyjs bin/light-map.js -m -c -screw-ie8 > example/light-map.min.js",
39 | "build": "npm run bundle && npm run uglify"
40 | },
41 |
42 | "demos": [
43 | "./example/index.html"
44 | ],
45 |
46 | "readme": "light map\n=============\n\nminimal, lightweight, self contained XYZ tile map viewer with a 2d canvas renderer.\n\n### live example ###\n- [an example with the most common methods](http://nicoptere.github.io/light-map/example/) let's you set the lat/lon/zoom, change the width/height of the canvas and monitor the load progress.\n- [a basic example of controls](http://nicoptere.github.io/light-map/example/controls.html) you should be able to drag the map around.\n- [an example of retina support](http://nicoptere.github.io/light-map/example/retina.html) first I thought it wasn't much but it seems to be a big deal. it's based on the devicePixelRatio, ideally you should use a @2x provider but I did an internal nearest neighbour resize.\n- [an example with a vignette](http://nicoptere.github.io/light-map/example/basic.html) as the map output is a Canvas2D, this shows how to post process the output.\n\n### more info ###\n[explanation and examples](http://nicoptere.github.io/light-map/)\n\n### basic example ###\n\n```js\n\n\n\n```\n\n### vignette example ###\n\n```js\n\n\n\n\n```\n\n### npm module installation ###\n```\nnpm install light-map --save\n```\n\n### related ###\n[Python library to perform Mercator conversions](http://www.maptiler.org/google-maps-coordinates-tile-bounds-projection/)\n\n[Quad keys explained](https://msdn.microsoft.com/en-us/library/bb259689.aspx)\n\nnpm [globalMercator](https://github.com/davvo/globalmercator/blob/master/globalmercator.js)\n\n### License ###\n\nThis content is released under the [MIT License](http://opensource.org/licenses/MIT).\n",
47 |
48 | "repository": {
49 | "type": "git",
50 | "url": "https://github.com/nicoptere/light-map.git"
51 | },
52 | "license": "MIT",
53 |
54 | "bugs": {
55 | "url": "https://github.com/nicoptere/light-map/issues"
56 | },
57 |
58 | "homepage": "https://github.com/nicoptere/light-map"
59 | }
60 |
--------------------------------------------------------------------------------