check-in bower_components

This commit is contained in:
Norbert Renner 2014-01-27 18:46:51 +01:00
parent 4cc16bccd0
commit 9e08e74132
101 changed files with 90960 additions and 0 deletions

View file

@ -0,0 +1,13 @@
{
"name": "leaflet-gpx",
"homepage": "https://github.com/mpetazzoni/leaflet-gpx",
"_release": "f0622fa76e",
"_resolution": {
"type": "branch",
"branch": "master",
"commit": "f0622fa76ec80639b530a2348a7b291ed05a4418"
},
"_source": "git://github.com/mpetazzoni/leaflet-gpx.git",
"_target": "*",
"_originalSource": "mpetazzoni/leaflet-gpx"
}

24
bower_components/leaflet-gpx/LICENSE vendored Normal file
View file

@ -0,0 +1,24 @@
Copyright (C) 2011-2012 Pavel Shramov
Copyright (C) 2013 Maxime Petazzoni <maxime.petazzoni@bulix.org>
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

126
bower_components/leaflet-gpx/README.md vendored Normal file
View file

@ -0,0 +1,126 @@
GPX plugin for Leaflet
======================
[Leaflet](http://www.leafletjs.com) is a Javascript library for displaying
interactive maps. This plugin, based on the work of [Pavel
Shramov](http://github.com/shramov) and his
[leaflet-plugins](http://github.com/shramov/leaflet-plugins), it allows for the
analysis and parsing of a GPX track in order to display it as a Leaflet map
layer. As it parses the GPX data, it will record information about the recorded
track, including total time, moving time, total distance, elevation stats and
heart-rate.
GPX parsing will automatically handle pauses in the track with a default
tolerance interval of 15 seconds between points. You can configure this
interval by setting `max_point_interval`, in milliseconds, in the options
passed to the `GPX` constructor.
I've put together a complete example as a
[demo](http://mpetazzoni.github.com/leaflet-gpx/).
License
-------
`leaflet-gpx` is under the *BSD 2-clause license*. Please refer to the
attached LICENSE file and/or to the copyright header in gpx.js for more
information.
Usage
-----
Usage is very simple. Let's consider we have a Leaflet map:
```javascript
var map = L.map('map');
L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: 'Map data &copy; <a href="http://www.osm.org">OpenStreetMap</a>'
}).addTo(map);
```
Displaying the GPX track is as easy as:
```javascript
var gpx = '...'; // URL to your GPX file or the GPX itself
new L.GPX(gpx, {async: true}).on('loaded', function(e) {
map.fitBounds(e.target.getBounds());
}).addTo(map);
```
If you want to display additional information about the GPX track, you can do
so in the 'loaded' event handler, calling one of the following methods on the
`GPX` object `e.target`:
* `get_name()`: returns the name of the GPX track
* `get_distance()`: returns the total track distance, in meters
* `get_start_time()`: returns a Javascript `Date` object representing the
starting time
* `get_end_time()`: returns a Javascript `Date` object representing when the
last point was recorded
* `get_moving_time()`: returns the moving time, in milliseconds
* `get_total_time()`: returns the total track time, in milliseconds
* `get_moving_pace()`: returns the average moving pace in milliseconds per km
* `get_elevation_gain()`: returns the cumulative elevation gain, in meters
* `get_elevation_loss()`: returns the cumulative elevation loss, in meters
* `get_average_hr()`: returns the average heart rate (if available)
If you're not a fan of the metric system, you also have the following methods
at your disposal:
* `get_distance_imp()`: returns the total track distance in miles
* `get_moving_pace_imp()`: returns the average moving pace in milliseconds per
mile
The reason why these methods return milliseconds is that you have at your
disposal a nice helper method to format a duration in milliseconds into a cool
string like `3:07'48"` or `59'32.431`:
* `get_duration_string(duration, hidems)`, where `duration` is in
milliseconds and `hidems` is an optional boolean you can use to request never
to display millisecond precision.
You can also get full elevation and heartrate data with:
* `get_elevation_data()` and `get_elevation_data_imp()`
* `get_heartrate_data()` and `get_heartrate_data_imp()`
These methods all return an array of points `[distance, value, tooltip]` where
the distance is either in kilometers or in miles and the elevation in meters of
feet, depending on whether you use the `_imp` variant or not. Heart rate,
obviously, doesn't change.
About marker icons
------------------
By default `gpx.js` will use `pin-icon-start.png`, `pin-icon-end.png` and
`pin-shadow.png` as the marker icons URLs for, respectively, the start marker,
the end marker and their drop shadow. Since it might not be convenient that
these images have to reside under the same directory as your HTML page, it is
possible to override the marker icon URLs and sizes by passing a
`marker_options` object to the `GPX` options object.
The field names are the same as for custom Leaflet icons, as explained in the
[Markers with custom icons](http://leafletjs.com/examples/custom-icons.html)
page in Leaflet's documentation. The only difference is that instead of
`iconUrl` you should specify `startIconUrl` and `endIconUrl` for the start and
end markers, respectively.
Note that you do not need to override all the marker icon options as `gpx.js`
will use sensible defaults with sizes matching the provided icon images. Here
is how you would override the URL of the provided icons if you decided to place
them in an `images/` directory:
```javascript
var url = '...'; // URL to your GPX file
new L.GPX(url, {
async: true,
marker_options: {
startIconUrl: 'images/pin-icon-start.png',
endIconUrl: 'images/pin-icon-end.png',
shadowUrl: 'images/pin-shadow.png'
}
}).on('loaded', function(e) {
map.fitBounds(e.target.getBounds());
}).addTo(map);
```

337
bower_components/leaflet-gpx/gpx.js vendored Normal file
View file

@ -0,0 +1,337 @@
/**
* Copyright (C) 2011-2012 Pavel Shramov
* Copyright (C) 2013 Maxime Petazzoni <maxime.petazzoni@bulix.org>
* All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Thanks to Pavel Shramov who provided the initial implementation and Leaflet
* integration. Original code was at https://github.com/shramov/leaflet-plugins.
*
* It was then cleaned-up and modified to record and make available more
* information about the GPX track while it is being parsed so that the result
* can be used to display additional information about the track that is
* rendered on the Leaflet map.
*/
var _MAX_POINT_INTERVAL_MS = 15000;
var _SECOND_IN_MILLIS = 1000;
var _MINUTE_IN_MILLIS = 60 * _SECOND_IN_MILLIS;
var _HOUR_IN_MILLIS = 60 * _MINUTE_IN_MILLIS;
var _DEFAULT_MARKER_OPTS = {
startIconUrl: 'pin-icon-start.png',
endIconUrl: 'pin-icon-end.png',
shadowUrl: 'pin-shadow.png',
iconSize: [33, 50],
shadowSize: [50, 50],
iconAnchor: [16, 45],
shadowAnchor: [16, 47]
};
var _DEFAULT_POLYLINE_OPTS = {
color:'blue'
};
L.GPX = L.FeatureGroup.extend({
initialize: function(gpx, options) {
options.max_point_interval = options.max_point_interval || _MAX_POINT_INTERVAL_MS;
options.marker_options = this._merge_objs(
_DEFAULT_MARKER_OPTS,
options.marker_options || {});
options.polyline_options = this._merge_objs(
_DEFAULT_POLYLINE_OPTS,
options.polyline_options || {});
L.Util.setOptions(this, options);
// Base icon class for track pins.
L.GPXTrackIcon = L.Icon.extend({ options: options.marker_options });
this._gpx = gpx;
this._layers = {};
this._info = {
name: null,
length: 0.0,
elevation: {gain: 0.0, loss: 0.0, _points: []},
hr: {avg: 0, _total: 0, _points: []},
duration: {start: null, end: null, moving: 0, total: 0},
};
if (gpx) {
this._parse(gpx, options, this.options.async);
}
},
get_duration_string: function(duration, hidems) {
var s = '';
if (duration >= _HOUR_IN_MILLIS) {
s += Math.floor(duration / _HOUR_IN_MILLIS) + ':';
duration = duration % _HOUR_IN_MILLIS;
}
var mins = Math.floor(duration / _MINUTE_IN_MILLIS);
duration = duration % _MINUTE_IN_MILLIS;
if (mins < 10) s += '0';
s += mins + '\'';
var secs = Math.floor(duration / _SECOND_IN_MILLIS);
duration = duration % _SECOND_IN_MILLIS;
if (secs < 10) s += '0';
s += secs;
if (!hidems && duration > 0) s += '.' + Math.round(Math.floor(duration)*1000)/1000;
else s += '"';
return s;
},
to_miles: function(v) { return v / 1.60934; },
to_ft: function(v) { return v * 3.28084; },
m_to_km: function(v) { return v / 1000; },
m_to_mi: function(v) { return v / 1609.34; },
get_name: function() { return this._info.name; },
get_distance: function() { return this._info.length; },
get_distance_imp: function() { return this.to_miles(this.m_to_km(this.get_distance())); },
get_start_time: function() { return this._info.duration.start; },
get_end_time: function() { return this._info.duration.end; },
get_moving_time: function() { return this._info.duration.moving; },
get_total_time: function() { return this._info.duration.total; },
get_moving_pace: function() { return this.get_moving_time() / this.m_to_km(this.get_distance()); },
get_moving_pace_imp: function() { return this.get_moving_time() / this.get_distance_imp(); },
get_elevation_gain: function() { return this._info.elevation.gain; },
get_elevation_loss: function() { return this._info.elevation.loss; },
get_elevation_data: function() {
var _this = this;
return this._info.elevation._points.map(
function(p) { return _this._prepare_data_point(p, _this.m_to_km, null,
function(a, b) { return a.toFixed(2) + ' km, ' + b.toFixed(0) + ' m'; });
});
},
get_elevation_data_imp: function() {
var _this = this;
return this._info.elevation._points.map(
function(p) { return _this._prepare_data_point(p, _this.m_to_mi, _this.to_ft,
function(a, b) { return a.toFixed(2) + ' mi, ' + b.toFixed(0) + ' ft'; });
});
},
get_average_hr: function() { return this._info.hr.avg; },
get_heartrate_data: function() {
var _this = this;
return this._info.hr._points.map(
function(p) { return _this._prepare_data_point(p, _this.m_to_km, null,
function(a, b) { return a.toFixed(2) + ' km, ' + b.toFixed(0) + ' bpm'; });
});
},
get_heartrate_data_imp: function() {
var _this = this;
return this._info.hr._points.map(
function(p) { return _this._prepare_data_point(p, _this.m_to_mi, null,
function(a, b) { return a.toFixed(2) + ' mi, ' + b.toFixed(0) + ' bpm'; });
});
},
// Private methods
_merge_objs: function(a, b) {
var _ = {};
for (var attr in a) { _[attr] = a[attr]; }
for (var attr in b) { _[attr] = b[attr]; }
return _;
},
_prepare_data_point: function(p, trans1, trans2, trans_tooltip) {
var r = [trans1 && trans1(p[0]) || p[0], trans2 && trans2(p[1]) || p[1]];
r.push(trans_tooltip && trans_tooltip(r[0], r[1]) || (r[0] + ': ' + r[1]));
return r;
},
_load_xml: function(url, cb, options, async) {
if (async == undefined) async = this.options.async;
if (options == undefined) options = this.options;
var req = new window.XMLHttpRequest();
req.open('GET', url, async);
try {
req.overrideMimeType('text/xml'); // unsupported by IE
} catch(e) {}
req.onreadystatechange = function() {
if (req.readyState != 4) return;
if(req.status == 200) cb(req.responseXML, options);
};
req.send(null);
},
_parse: function(input, options, async) {
var _this = this;
var cb = function(gpx, options) {
var layers = _this._parse_gpx_data(gpx, options);
if (!layers) return;
_this.addLayer(layers);
_this.fire('loaded');
}
if (input.substr(0,1)==='<') { // direct XML has to start with a <
var parser = new DOMParser();
setTimeout(function() {
cb(parser.parseFromString(input, "text/xml"), options);
});
} else {
this._load_xml(input, cb, options, async);
}
},
_parse_gpx_data: function(xml, options) {
var j, i, el, layers = [];
var tags = [['rte','rtept'], ['trkseg','trkpt']];
var name = xml.getElementsByTagName('name');
if (name.length > 0) {
this._info.name = name[0].textContent;
}
for (j = 0; j < tags.length; j++) {
el = xml.getElementsByTagName(tags[j][0]);
for (i = 0; i < el.length; i++) {
var coords = this._parse_trkseg(el[i], xml, options, tags[j][1]);
if (coords.length === 0) continue;
// add track
var l = new L.Polyline(coords, options.polyline_options);
this.fire('addline', { line: l })
layers.push(l);
if (options.marker_options.startIconUrl) {
// add start pin
var p = new L.Marker(coords[0], {
clickable: false,
icon: new L.GPXTrackIcon({iconUrl: options.marker_options.startIconUrl})
});
this.fire('addpoint', { point: p });
layers.push(p);
}
if (options.marker_options.endIconUrl) {
// add end pin
p = new L.Marker(coords[coords.length-1], {
clickable: false,
icon: new L.GPXTrackIcon({iconUrl: options.marker_options.endIconUrl})
});
this.fire('addpoint', { point: p });
layers.push(p);
}
}
}
this._info.hr.avg = Math.round(this._info.hr._total / this._info.hr._points.length);
if (!layers.length) return;
var layer = layers[0];
if (layers.length > 1)
layer = new L.FeatureGroup(layers);
return layer;
},
_parse_trkseg: function(line, xml, options, tag) {
var el = line.getElementsByTagName(tag);
if (!el.length) return [];
var coords = [];
var last = null;
for (var i = 0; i < el.length; i++) {
var _, ll = new L.LatLng(
el[i].getAttribute('lat'),
el[i].getAttribute('lon'));
ll.meta = { time: null, ele: null, hr: null };
_ = el[i].getElementsByTagName('time');
if (_.length > 0) {
ll.meta.time = new Date(Date.parse(_[0].textContent));
}
_ = el[i].getElementsByTagName('ele');
if (_.length > 0) {
ll.meta.ele = parseFloat(_[0].textContent);
}
_ = el[i].getElementsByTagNameNS('*', 'hr');
if (_.length > 0) {
ll.meta.hr = parseInt(_[0].textContent);
this._info.hr._points.push([this._info.length, ll.meta.hr]);
this._info.hr._total += ll.meta.hr;
}
this._info.elevation._points.push([this._info.length, ll.meta.ele]);
this._info.duration.end = ll.meta.time;
if (last != null) {
this._info.length += this._dist3d(last, ll);
var t = ll.meta.ele - last.meta.ele;
if (t > 0) this._info.elevation.gain += t;
else this._info.elevation.loss += Math.abs(t);
t = Math.abs(ll.meta.time - last.meta.time);
this._info.duration.total += t;
if (t < options.max_point_interval) this._info.duration.moving += t;
} else {
this._info.duration.start = ll.meta.time;
}
last = ll;
coords.push(ll);
}
return coords;
},
_dist2d: function(a, b) {
var R = 6371000;
var dLat = this._deg2rad(b.lat - a.lat);
var dLon = this._deg2rad(b.lng - a.lng);
var r = Math.sin(dLat/2) *
Math.sin(dLat/2) +
Math.cos(this._deg2rad(a.lat)) *
Math.cos(this._deg2rad(b.lat)) *
Math.sin(dLon/2) *
Math.sin(dLon/2);
var c = 2 * Math.atan2(Math.sqrt(r), Math.sqrt(1-r));
var d = R * c;
return d;
},
_dist3d: function(a, b) {
var planar = this._dist2d(a, b);
var height = Math.abs(b.meta.ele - a.meta.ele);
return Math.sqrt(Math.pow(planar, 2) + Math.pow(height, 2));
},
_deg2rad: function(deg) {
return deg * Math.PI / 180;
},
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 842 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 945 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,14 @@
{
"name": "shramov-plugins-permalink",
"homepage": "https://github.com/shramov/leaflet-plugins",
"version": "1.0.1",
"_release": "1.0.1",
"_resolution": {
"type": "version",
"tag": "v1.0.1",
"commit": "a03de1df77d53c58dd7194bf7e1ddbc100231ac2"
},
"_source": "git://github.com/shramov/leaflet-plugins.git",
"_target": "*",
"_originalSource": "leaflet-plugins"
}

View file

@ -0,0 +1,22 @@
Copyright (c) 2011-2012, Pavel Shramov
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are
permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of
conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list
of conditions and the following disclaimer in the documentation and/or other materials
provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -0,0 +1,41 @@
Leaflet plugins
===============
== What's it?
Miscellaneous plugins for Leaflet library for services that need to display
route information and need satellite imagery from different providers.
Currently it consists of:
- Vector layers (`layer/vector/`):
* GPX
* KML
- Providers (`layer/tile`):
* Google - using Google Maps API v3;
* Yandex - using Yandex Maps API v2;
* Bing - with proper attribution.
All these providers are implemented with respect to terms of use.
Also there are some useful control plugins (`control/`):
* Permalink - OpenLayers compatible permanent link with support for storing
location data in hash part (#lat=...);
* Scale - scale ruler which looks like one on paper maps.
== How to get?
http://psha.org.ru/cgit/psha/leaflet-plugins.git
or
http://github.com/shramov/leaflet-plugins.git
== Russian docs
See link:http://psha.org.ru/b/leaflet-plugins.ru.html[russian documentation] for more info.
////////////////////////////////////
vim: sts=4 sw=4 et tw=80 ft=asciidoc
////////////////////////////////////

View file

@ -0,0 +1,28 @@
JS := $(wildcard ../layer/*/*.js ../layer/*.js ../control/*js)
#JS += control/Scale.js control/Distance.js
#JS += control/Permalink.js $(wildcard control/Permalink.*.js)
JS := $(shell python ./deps.py $(JS))
#JS_EXTRA := $(JS) $(wildcard layer/*.js)
all: compiled.yui.js compiled.closure.js
compile: $(patsubst %,compiled/%,$(JS))
closure/%: ../%
mkdir -p $(dir $@)
java -jar /tmp/closure.jar --compilation_level ADVANCED_OPTIMIZATIONS --charset UTF-8 --js $< --js_output_file $@.tmp
mv $@.tmp $@
yui/%: ../%
mkdir -p $(dir $@)
yui-compressor --charset UTF-8 -o $@.tmp $<
mv $@.tmp $@
compiled.yui.js: $(patsubst ../%,yui/%,$(JS))
compiled.closure.js: $(patsubst ../%,closure/%,$(JS))
compiled.%.js:
for f in $^; do cat $$f >> $@.tmp; echo >> $@.tmp; done
mv $@.tmp $@
.PHONY: all compile

View file

@ -0,0 +1,26 @@
#!/usr/bin/env python
# vim: sts=4 sw=4 et
import os, sys
printed = set()
def includes(f):
d = os.path.dirname(f)
for l in open(f):
if l.startswith('//#include'):
yield os.path.join(d, l.strip().split(None, 1)[1].strip(""""'"""))
work = list(sys.argv[1:])
while work:
f = work.pop(0)
if f in printed:
continue
i = list(filter(lambda x: x not in printed, includes(f)))
if i:
work = i + [f] + work
continue
printed.add(f)
print f

View file

@ -0,0 +1,127 @@
L.Control.Distance = L.Control.extend({
options: {
position: 'topleft',
popups: true
},
initialize: function (options) {
L.Util.setOptions(this, options);
this._line = new L.Polyline([], {editable: true});
this._line.on('edit', this._update, this);
this._line.on('click', function(e) {});
this._active = false;
},
getLine: function() { return this._line; },
onAdd: function(map) {
var className = 'leaflet-control-distance',
container = this._container = L.DomUtil.create('div', className);
function cb() {
if (this._active)
this._calc_disable();
else
this._calc_enable();
}
var link = this._link = this._createButton('Edit', 'leaflet-control-distance leaflet-control-distance-edit', container, cb, this);
var del = this._link_delete = this._createButton('Delete', 'leaflet-control-distance leaflet-control-distance-delete', container, this._reset, this);
var text = this._text = L.DomUtil.create('div', 'leaflet-control-distance-text', container);
//text.style.display = 'inline';
//text.style.float = 'right';
this._map.addLayer(this._line);
this._calc_disable();
return container;
},
_createButton: function (title, className, container, fn, context) {
var link = L.DomUtil.create('a', className, container);
link.href = '#';
link.title = title;
L.DomEvent
.addListener(link, 'click', L.DomEvent.stopPropagation)
.addListener(link, 'click', L.DomEvent.preventDefault)
.addListener(link, 'click', fn, context);
return link;
},
onRemove: function(map) {
this._calc_disable();
},
_calc_enable: function() {
this._map.on('click', this._add_point, this);
this._map.getContainer().style.cursor = 'crosshair';
//this._map.addLayer(this._line);
L.DomUtil.addClass(this._link, 'leaflet-control-distance-active');
this._container.appendChild(this._link_delete);
this._container.appendChild(this._text);
this._active = true;
this._line.editing.enable();
if (!this._map.hasLayer(this._line))
this._map.addLayer(this._line);
this._update();
},
_calc_disable: function() {
this._map.off('click', this._add_point, this);
//this._map.removeLayer(this._line);
this._map.getContainer().style.cursor = 'default';
this._container.removeChild(this._link_delete);
this._container.removeChild(this._text);
L.DomUtil.removeClass(this._link, 'leaflet-control-distance-active');
this._active = false;
this._line.editing.disable();
},
_add_point: function (e) {
var len = this._line.getLatLngs().length;
this._line.addLatLng(e.latlng);
this._line.editing.updateMarkers();
this._line.fire('edit', {});
},
_reset: function(e) {
this._line.setLatLngs([]);
this._line.fire('edit', {});
this._line.redraw();
this._line.editing.updateMarkers();
},
_update: function(e) {
console.info("Update");
this._text.textContent = this._d2txt(this._distance_calc());
},
_d2txt: function(d) {
if (d < 2000)
return d.toFixed(0) + " m";
else
return (d/1000).toFixed(1) + " km";
},
_distance_calc: function(e) {
var ll = this._line.getLatLngs();
var d = 0, p = null;
for (var i = 0; i < ll.length; i++) {
if (i)
d += p.distanceTo(ll[i]);
if (this.options.popups) {
var m = this._line.editing._markers[i];
if (m) {
m.bindPopup(this._d2txt(d));
m.on('mouseover', m.openPopup, m);
m.on('mouseout', m.closePopup, m);
}
}
p = ll[i];
}
return d;
}
});

View file

@ -0,0 +1,60 @@
/*
* Add async initialization of layers to L.Control.Layers
*/
L.Control.Layers.include({
_loadScripts: function(scripts, cb, args) {
if (!scripts || scripts.length == 0)
return cb(args);
var _this = this, s = scripts.pop(), c;
c = L.Control.Layers._script_cache[s];
if (c === undefined) {
c = {url: s, wait: []};
//console.info("Load " + s);
var script = document.createElement('script');
script.src = s;
script.type = 'text/javascript';
script.onload = function () {
var i = 0;
for (i = 0; i < c.wait.length; i++)
c.wait[i]();
}
c.e = script;
document.getElementsByTagName('head')[0].appendChild(script);
}
function _cb() { _this._loadScripts(scripts, cb, args); }
c.wait.push(_cb);
if (c.e.readyState == "completed")
_cb();
L.Control.Layers._script_cache[s] = c;
},
addLayerDef: function(name, def) {
if (this._layer_defs === undefined)
this._layer_defs = {};
this._layer_defs[name] = def;
},
addLayerDefs: function(defs) {
if (this._layer_defs === undefined)
this._layer_defs = {};
L.Util.extend(this._layer_defs, defs);
},
loadLayer: function(name, deflt) {
var _this = this, l = this._layer_defs[name];
l['default'] = deflt;
this._loadScripts(l.js.reverse(), function(l) {_this._loadLayer(l)}, l);
},
_loadLayer: function(l) {
var x = l.init();
if (l['default'] && this._map)
this._map.addLayer(x);
if (!l.overlay)
this.addBaseLayer(x, l.name);
else
this.addOverlay(x, l.name);
}
});
L.Control.Layers._script_cache = {};

View file

@ -0,0 +1,76 @@
//#include "Permalink.js
L.Control.Permalink.include({
/*
options: {
useMarker: true,
markerOptions: {}
},
*/
initialize_layer: function() {
//console.info("Initialize layer");
this.on('update', this._set_layer, this);
this.on('add', this._onadd_layer, this);
},
_onadd_layer: function(e) {
//console.info("onAdd::layer", e);
this._map.on('layeradd', this._update_layer, this);
this._map.on('layerremove', this._update_layer, this);
this._update_layer();
},
_update_layer: function() {
if (!this.options.layers) return;
//console.info(this.options.layers);
var layer = this.options.layers.currentBaseLayer();
if (layer)
this._update({layer: layer.name});
},
_set_layer: function(e) {
//console.info("Set layer", e);
var p = e.params;
if (!this.options.layers || !p.layer) return;
this.options.layers.chooseBaseLayer(p.layer);
}
});
L.Control.Layers.include({
chooseBaseLayer: function(name) {
var layer, obj;
for (var i in this._layers) {
if (!this._layers.hasOwnProperty(i))
continue;
obj = this._layers[i];
if (!obj.overlay && obj.name == name)
layer = obj.layer;
}
if (!layer || this._map.hasLayer(layer))
return;
for (var i in this._layers) {
if (!this._layers.hasOwnProperty(i))
continue;
obj = this._layers[i];
if (!obj.overlay && this._map.hasLayer(obj.layer))
this._map.removeLayer(obj.layer)
}
this._map.addLayer(layer)
this._update();
},
currentBaseLayer: function() {
for (var i in this._layers) {
if (!this._layers.hasOwnProperty(i))
continue;
var obj = this._layers[i];
//console.info("Layer: ", obj.name, obj);
if (obj.overlay) continue;
if (!obj.overlay && this._map.hasLayer(obj.layer))
return obj;
}
}
});

View file

@ -0,0 +1,49 @@
//#include "Permalink.js
L.Control.Permalink.include({
/*
options: {
line: null
},
*/
initialize_line: function() {
this.on('update', this._set_line, this);
this.on('add', this._onadd_line, this);
},
_onadd_line: function(e) {
//console.info("onAdd::line", e);
if (!this.options.line) return;
this.options.line.on('edit', this._update_line, this);
this._update_line()
},
_update_line: function() {
if (!this.options.line) return;
var line = this.options.line;
if (!line) return;
var text = [], coords = line.getLatLngs();
if (!coords.length)
return this._update({line: null});
for (var i in coords)
text.push(coords[i].lat.toFixed(4) + "," + coords[i].lng.toFixed(4))
this._update({line: text.join(';')});
},
_set_line: function(e) {
//console.info("Set line", e.params.line);
var p = e.params, l = this.options.line;
if (!l || !p.line) return;
var coords = [], text = p.line.split(';');
for (var i in text) {
var ll = text[i].split(',');
if (ll.length != 2) continue;
coords.push(new L.LatLng(ll[0], ll[1]));
}
if (!coords.length) return;
l.setLatLngs(coords);
if (!this._map.hasLayer(l))
this._map.addLayer(l);
}
});

View file

@ -0,0 +1,31 @@
//#include "Permalink.js
L.Control.Permalink.include({
/*
options: {
useMarker: true,
markerOptions: {}
},
*/
initialize_marker: function() {
//console.info("Initialize marker");
this.on('update', this._set_marker, this);
},
_set_marker: function(e) {
//console.info("Set marker", e);
var p = e.params;
//if (!this.options.useMarker) return;
if (this._marker) return;
if (p.marker != 1) return;
if (p.mlat !== undefined && p.mlon !== undefined)
return this._update({mlat: null, mlon: null,
lat: p.mlat, lon: p.mlon, marker: 1});
this._marker = new L.Marker(new L.LatLng(p.lat, p.lon),
this.options.markerOptions);
this._marker.bindPopup("<a href='" + this._update_href() + "'>" + this.options.text + "</a>");
this._map.addLayer(this._marker);
this._update({marker: null});
}
});

View file

@ -0,0 +1,163 @@
L.Control.Permalink = L.Control.extend({
includes: L.Mixin.Events,
options: {
position: "bottomleft",
useAnchor: true,
useLocation: false,
text: "Permalink"
},
initialize: function(options) {
L.Util.setOptions(this, options);
this._params = {};
this._set_urlvars();
this.on("update", this._set_center, this);
for (var i in this) {
if (typeof(i) === "string" && i.indexOf('initialize_') == 0)
this[i]();
}
},
onAdd: function(map) {
this._container = L.DomUtil.create('div', 'leaflet-control-attribution leaflet-control-permalink');
L.DomEvent.disableClickPropagation(this._container);
this._map = map;
this._href = L.DomUtil.create('a', null, this._container);
this._href.innerHTML = this.options.text
map.on('moveend', this._update_center, this);
this.fire("update", {params: this._params})
this._update_center();
if (this.options.useAnchor && 'onhashchange' in window) {
var _this = this, fn = window.onhashchange;
window.onhashchange = function() {
_this._set_urlvars();
if (fn) return fn();
}
}
this.fire('add', {map: map});
return this._container;
},
_update_center: function() {
if (!this._map) return;
var center = this._round_point(this._map.getCenter());
this._update({zoom: this._map.getZoom(), lat: center.lat, lon: center.lng});
},
_update_href: function() {
var params = L.Util.getParamString(this._params);
var sep = '?';
if (this.options.useAnchor) sep = '#';
var url = this._url_base + sep + params.slice(1);
if (this._href) this._href.setAttribute('href', url);
if (this.options.useLocation)
location.replace('#' + params.slice(1));
return url;
},
_round_point : function(point) {
var bounds = this._map.getBounds(), size = this._map.getSize();
var ne = bounds.getNorthEast(), sw = bounds.getSouthWest();
var round = function (x, p) {
if (p == 0) return x;
shift = 1;
while (p < 1 && p > -1) {
x *= 10;
p *= 10;
shift *= 10;
}
return Math.floor(x)/shift;
}
point.lat = round(point.lat, (ne.lat - sw.lat) / size.y);
point.lng = round(point.lng, (ne.lng - sw.lng) / size.x);
return point;
},
_update: function(obj, source) {
//console.info("Update", obj, this._params);
for(var i in obj) {
if (!obj.hasOwnProperty(i)) continue;
if (obj[i] != null && obj[i] != undefined)
this._params[i] = obj[i]
else
delete this._params[i];
}
this._update_href();
},
_set_urlvars: function()
{
this._url_base = window.location.href.split('#')[0].split('?')[0];
var p;
if (this.options.useAnchor)
p = L.UrlUtil.queryParse(L.UrlUtil.hash());
else
p = L.UrlUtil.queryParse(L.UrlUtil.query());
function eq(x, y) {
for(var i in x)
if (x.hasOwnProperty(i) && x[i] != y[i])
return false;
return true;
}
if (eq(p, this._params) && eq(this._params, p))
return;
this._params = p;
this._update_href();
this.fire("update", {params: this._params})
},
_set_center: function(e)
{
//console.info("Update center", e);
var params = e.params;
if (params.zoom == undefined ||
params.lat == undefined ||
params.lon == undefined) return;
this._map.setView(new L.LatLng(params.lat, params.lon), params.zoom);
}
});
L.UrlUtil = {
queryParse: function(s) {
var p = {};
var sep = "&";
if (s.search("&amp;") != -1)
sep = "&amp;";
var params = s.split(sep);
for(var i = 0; i < params.length; i++) {
var tmp = params[i].split('=');
if (tmp.length != 2) continue;
p[tmp[0]] = decodeURI(tmp[1]);
}
return p;
},
query: function() {
var href = window.location.href.split('#')[0], idx = href.indexOf('?');
if (idx < 0)
return '';
return href.slice(idx+1);
},
hash: function() { return window.location.hash.slice(1) },
updateParamString: function (q, obj) {
var p = L.UrlUtil.queryParse(q);
for (var i in obj) {
if (obj.hasOwnProperty(i))
p[i] = obj[i];
}
return L.Util.getParamString(p).slice(1);
}
};

View file

@ -0,0 +1,118 @@
L.Control.Scale = L.Control.extend({
options: {
position: "bottomleft",
useCanvas: null,
width: 100
},
initialize: function(options) {
L.Util.setOptions(this, options);
},
onAdd: function(map) {
this._map = map;
this._container = L.DomUtil.create('div', 'leaflet-control-attribution leaflet-control-scale');
this._label = L.DomUtil.create('div', null, this._container);
this._label.style.textAlign = 'right';
if (!this.options.useCanvas && this.options.useCanvas != false)
this.options.useCanvas = "HTMLCanvasElement" in window;
if (this.options.useCanvas) {
this._canvas = L.DomUtil.create('canvas', 'leaflet-canvas-marker', this._container);
} else {
this._canvas = L.DomUtil.create('div', null, this._container);
this._canvas.style.border = "1px solid black";
this._canvas.innerHTML = "&nbsp;";
//this._canvas.style.padding = "none";
//this._canvas.style.margin = "none";
//this._canvas.style.width = 100;
//this._canvas.style.height = 5;
}
map.on('zoomend', this._update, this);
this._update();
return this._container;
},
onRemove: function(map) {
map._container.removeChild(this._label);
map._container.removeChild(this._canvas);
map.off('zoomend', this._reset);
},
getPosition: function() {
return this.options.position;
},
getContainer: function() {
return this._container;
},
_update: function() {
if (!this._map) return;
var size = this.options.width;
var b = this._map.getBounds(), pb = this._map.getPixelBounds();
var width = this._deg_length(b.getNorthEast(), b.getNorthWest());
width = size * width / (pb.max.x - pb.min.x);
var iw = this._round(width);
if (iw >= 1)
this._label.innerHTML = iw + " km";
else
this._label.innerHTML = Math.round(1000 * iw) + " m";
size = size * iw / width;
if (this.options.useCanvas) {
this._canvas.width = size+1;
this._canvas.height = 10+1;
var ctx = this._canvas.getContext("2d");
this._draw(ctx, size, 5);
} else {
this._canvas.style.width = size;
this._canvas.style.height = 5;
}
},
_draw: function(ctx, width, height) {
ctx.beginPath();
ctx.fillStyle = ctx.strokeStyle = '#000';
ctx.lineWidth = 0.5;
ctx.strokeRect(0, height, width/2, height);
ctx.fillRect(0, height, width/2, height);
ctx.strokeRect(width/2, height, width/2, height);
ctx.moveTo(0, 0);
ctx.lineTo(0, height);
ctx.moveTo(width/2, 0);
ctx.lineTo(width/2, height);
ctx.moveTo(width, 0);
ctx.lineTo(width, height);
ctx.stroke();
},
_deg_length : function(p1, p2) {
var deglen = 111.12 * L.LatLng.RAD_TO_DEG;
var p1lat = p1.lat * L.LatLng.DEG_TO_RAD,
p1lng = p1.lng * L.LatLng.DEG_TO_RAD,
p2lat = p2.lat * L.LatLng.DEG_TO_RAD,
p2lng = p2.lng * L.LatLng.DEG_TO_RAD;
return deglen * Math.acos(Math.sin(p1lat) * Math.sin(p2lat) +
Math.cos(p1lat) * Math.cos(p2lat) * Math.cos(p2lng - p1lng));
},
_round : function (x) {
var div = 1;
while (div < x) div *= 10;
while (div > x) div /= 10;
var s = div;
while (s < x) s += div;
if (s > 5 * div) s = 10 * div;
return s;
}
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,48 @@
.leaflet-control-distance {
-moz-border-radius: 7px;
-webkit-border-radius: 7px;
border-radius: 7px;
padding: 5px;
background-color: rgba(0, 0, 0, 0.25);
}
.leaflet-control-distance a, .leaflet-control-distance div {
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
border-radius: 4px;
padding: 2px;
background-position: 50% 50%;
background-repeat: no-repeat;
background-color: rgba(255, 255, 255, 0.75);
}
.leaflet-control-distance a {
width: 16px;
height: 16px;
display: inline-block;
}
.leaflet-control-distance div {
font: 11px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;
color: #333;
height: 16px;
margin-top: 5px;
white-space: nowrap;
}
.leaflet-control-distance-edit {
/* http://openclipart.org/detail/114727/ftview-ruler-by-anonymous */
background-image: url('measure.png');
}
.leaflet-control-distance-delete {
margin-left: 5px;
/* Based on http://openclipart.org/detail/34729/architetto----tasto-8-by-anonymous */
background-image: url('cross.png');
}
a.leaflet-control-distance-active {
background-color: yellow;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 B

View file

@ -0,0 +1,97 @@
.osb-popup {
font-family: sans-serif;
font-size: small;
max-width: 380px;
overflow: auto;
}
.osb-popup a:link, .osb-popup a:visited {
border-bottom: 1px dotted #C0C0C0;
text-decoration: none;
}
.osb-popup a:link {
color: #990000;
}
.osb-popup a:visited {
color: #500000;
}
.osb-popup a:link:hover, .osb-popup a:visited:hover {
border-bottom: 1px solid #C0C0C0;
color: red;
}
.osb-popup a:link:focus .osb-popup a:visited:focus {
outline: 1px dotted #C0C0C0;
}
.osb-popup a:link:active, .osb-popup a:visited:active {
border-bottom: 1px solid #F0F0F0;
color: red;
outline: 1px solid #C0C0C0;
}
.osb-popup h1 {
color: #176BC1;
font-size: medium;
margin-bottom: 7px;
margin-top: 0;
}
.osb-popup p {
margin-bottom: 0;
margin-top: 5px;
}
.osb-popup p.Comment {
margin-left: 15px;
margin-top: 5px;
}
.osb-popup p.Note {
border-top: 1px solid gray;
font-size: x-small;
margin-top: 7px;
padding-top: 3px;
}
.osb-popup form + p {
border-top: 1px solid gray;
margin-top: 15px;
padding-top: 10px;
}
.osb-popup ul {
border-top: 1px solid gray;
margin: 10px 0 0;
padding: 5px 0 0;
text-align: center;
}
.osb-popup ul li {
display: inline;
margin-left: 12px;
margin-right: 12px;
padding: 0;
}
.osb-popup form {
margin-bottom: 0;
margin-top: 10px;
}
.osb-popup form.osb-comment {
border-top: 1px solid gray;
padding-top: 5px;
}
.osb-popup form div {
margin-left: 15px;
margin-right: 15px;
margin-top: 5px;
}
.osb-popup form div.osb-footer {
margin-bottom: 0;
margin-top: 15px;
text-align: center;
}
.osb-popup span.osb-inputlabel {
display: inline-block;
margin-right: 10px;
min-width: 110px;
}
.osb-popup input[type="text"] {
min-width: 190px;
}
.osb-popup input[type="button"] {
margin-left: 3px;
margin-right: 3px;
min-width: 75px;
}

View file

@ -0,0 +1,915 @@
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<Document>
<name>KML Samples</name>
<open>1</open>
<description>Unleash your creativity with the help of these examples!</description>
<Style id="downArrowIcon">
<IconStyle>
<Icon>
<href>http://maps.google.com/mapfiles/kml/pal4/icon28.png</href>
</Icon>
</IconStyle>
</Style>
<Style id="globeIcon">
<IconStyle>
<Icon>
<href>http://maps.google.com/mapfiles/kml/pal3/icon19.png</href>
</Icon>
</IconStyle>
<LineStyle>
<width>2</width>
</LineStyle>
</Style>
<Style id="transPurpleLineGreenPoly">
<LineStyle>
<color>7fff00ff</color>
<width>4</width>
</LineStyle>
<PolyStyle>
<color>7f00ff00</color>
</PolyStyle>
</Style>
<Style id="yellowLineGreenPoly">
<LineStyle>
<color>7f00ffff</color>
<width>4</width>
</LineStyle>
<PolyStyle>
<color>7f00ff00</color>
</PolyStyle>
</Style>
<Style id="thickBlackLine">
<LineStyle>
<color>87000000</color>
<width>10</width>
</LineStyle>
</Style>
<Style id="redLineBluePoly">
<LineStyle>
<color>ff0000ff</color>
</LineStyle>
<PolyStyle>
<color>ffff0000</color>
</PolyStyle>
</Style>
<Style id="blueLineRedPoly">
<LineStyle>
<color>ffff0000</color>
</LineStyle>
<PolyStyle>
<color>ff0000ff</color>
</PolyStyle>
</Style>
<Style id="transRedPoly">
<LineStyle>
<width>1.5</width>
</LineStyle>
<PolyStyle>
<color>7d0000ff</color>
</PolyStyle>
</Style>
<Style id="transBluePoly">
<LineStyle>
<width>1.5</width>
</LineStyle>
<PolyStyle>
<color>7dff0000</color>
</PolyStyle>
</Style>
<Style id="transGreenPoly">
<LineStyle>
<width>1.5</width>
</LineStyle>
<PolyStyle>
<color>7d00ff00</color>
</PolyStyle>
</Style>
<Style id="transYellowPoly">
<LineStyle>
<width>1.5</width>
</LineStyle>
<PolyStyle>
<color>7d00ffff</color>
</PolyStyle>
</Style>
<Style id="noDrivingDirections">
<BalloonStyle>
<text><![CDATA[
<b>$[name]</b>
<br /><br />
$[description]
]]></text>
</BalloonStyle>
</Style>
<Folder>
<name>Placemarks</name>
<description>These are just some of the different kinds of placemarks with
which you can mark your favorite places</description>
<LookAt>
<longitude>-122.0839597145766</longitude>
<latitude>37.42222904525232</latitude>
<altitude>0</altitude>
<heading>-148.4122922628044</heading>
<tilt>40.5575073395506</tilt>
<range>500.6566641072245</range>
</LookAt>
<Placemark>
<name>Simple placemark</name>
<description>Attached to the ground. Intelligently places itself at the
height of the underlying terrain.</description>
<Point>
<coordinates>-122.0822035425683,37.42228990140251,0</coordinates>
</Point>
</Placemark>
<Placemark>
<name>Floating placemark</name>
<visibility>0</visibility>
<description>Floats a defined distance above the ground.</description>
<LookAt>
<longitude>-122.0839597145766</longitude>
<latitude>37.42222904525232</latitude>
<altitude>0</altitude>
<heading>-148.4122922628044</heading>
<tilt>40.5575073395506</tilt>
<range>500.6566641072245</range>
</LookAt>
<styleUrl>#downArrowIcon</styleUrl>
<Point>
<altitudeMode>relativeToGround</altitudeMode>
<coordinates>-122.084075,37.4220033612141,50</coordinates>
</Point>
</Placemark>
<Placemark>
<name>Extruded placemark</name>
<visibility>0</visibility>
<description>Tethered to the ground by a customizable
&quot;tail&quot;</description>
<LookAt>
<longitude>-122.0845787421525</longitude>
<latitude>37.42215078737763</latitude>
<altitude>0</altitude>
<heading>-148.4126684946234</heading>
<tilt>40.55750733918048</tilt>
<range>365.2646606980322</range>
</LookAt>
<styleUrl>#globeIcon</styleUrl>
<Point>
<extrude>1</extrude>
<altitudeMode>relativeToGround</altitudeMode>
<coordinates>-122.0857667006183,37.42156927867553,50</coordinates>
</Point>
</Placemark>
</Folder>
<Folder>
<name>Styles and Markup</name>
<visibility>0</visibility>
<description>With KML it is easy to create rich, descriptive markup to
annotate and enrich your placemarks</description>
<LookAt>
<longitude>-122.0845787422371</longitude>
<latitude>37.42215078726837</latitude>
<altitude>0</altitude>
<heading>-148.4126777488172</heading>
<tilt>40.55750733930874</tilt>
<range>365.2646826292919</range>
</LookAt>
<styleUrl>#noDrivingDirections</styleUrl>
<Document>
<name>Highlighted Icon</name>
<visibility>0</visibility>
<description>Place your mouse over the icon to see it display the new
icon</description>
<LookAt>
<longitude>-122.0856552124024</longitude>
<latitude>37.4224281311035</latitude>
<altitude>0</altitude>
<heading>0</heading>
<tilt>0</tilt>
<range>265.8520424250024</range>
</LookAt>
<Style id="highlightPlacemark">
<IconStyle>
<Icon>
<href>http://maps.google.com/mapfiles/kml/paddle/red-stars.png</href>
</Icon>
</IconStyle>
</Style>
<Style id="normalPlacemark">
<IconStyle>
<Icon>
<href>http://maps.google.com/mapfiles/kml/paddle/wht-blank.png</href>
</Icon>
</IconStyle>
</Style>
<StyleMap id="exampleStyleMap">
<Pair>
<key>normal</key>
<styleUrl>#normalPlacemark</styleUrl>
</Pair>
<Pair>
<key>highlight</key>
<styleUrl>#highlightPlacemark</styleUrl>
</Pair>
</StyleMap>
<Placemark>
<name>Roll over this icon</name>
<visibility>0</visibility>
<styleUrl>#exampleStyleMap</styleUrl>
<Point>
<coordinates>-122.0856545755255,37.42243077405461,0</coordinates>
</Point>
</Placemark>
</Document>
<Placemark>
<name>Descriptive HTML</name>
<visibility>0</visibility>
<description><![CDATA[Click on the blue link!<br><br>
Placemark descriptions can be enriched by using many standard HTML tags.<br>
For example:
<hr>
Styles:<br>
<i>Italics</i>,
<b>Bold</b>,
<u>Underlined</u>,
<s>Strike Out</s>,
subscript<sub>subscript</sub>,
superscript<sup>superscript</sup>,
<big>Big</big>,
<small>Small</small>,
<tt>Typewriter</tt>,
<em>Emphasized</em>,
<strong>Strong</strong>,
<code>Code</code>
<hr>
Fonts:<br>
<font color="red">red by name</font>,
<font color="#408010">leaf green by hexadecimal RGB</font>
<br>
<font size=1>size 1</font>,
<font size=2>size 2</font>,
<font size=3>size 3</font>,
<font size=4>size 4</font>,
<font size=5>size 5</font>,
<font size=6>size 6</font>,
<font size=7>size 7</font>
<br>
<font face=times>Times</font>,
<font face=verdana>Verdana</font>,
<font face=arial>Arial</font><br>
<hr>
Links:
<br>
<a href="http://earth.google.com/">Google Earth!</a>
<br>
or: Check out our website at www.google.com
<hr>
Alignment:<br>
<p align=left>left</p>
<p align=center>center</p>
<p align=right>right</p>
<hr>
Ordered Lists:<br>
<ol><li>First</li><li>Second</li><li>Third</li></ol>
<ol type="a"><li>First</li><li>Second</li><li>Third</li></ol>
<ol type="A"><li>First</li><li>Second</li><li>Third</li></ol>
<hr>
Unordered Lists:<br>
<ul><li>A</li><li>B</li><li>C</li></ul>
<ul type="circle"><li>A</li><li>B</li><li>C</li></ul>
<ul type="square"><li>A</li><li>B</li><li>C</li></ul>
<hr>
Definitions:<br>
<dl>
<dt>Google:</dt><dd>The best thing since sliced bread</dd>
</dl>
<hr>
Centered:<br><center>
Time present and time past<br>
Are both perhaps present in time future,<br>
And time future contained in time past.<br>
If all time is eternally present<br>
All time is unredeemable.<br>
</center>
<hr>
Block Quote:
<br>
<blockquote>
We shall not cease from exploration<br>
And the end of all our exploring<br>
Will be to arrive where we started<br>
And know the place for the first time.<br>
<i>-- T.S. Eliot</i>
</blockquote>
<br>
<hr>
Headings:<br>
<h1>Header 1</h1>
<h2>Header 2</h2>
<h3>Header 3</h3>
<h3>Header 4</h4>
<h3>Header 5</h5>
<hr>
Images:<br>
<i>Remote image</i><br>
<img src="//developers.google.com/kml/documentation/images/googleSample.png"><br>
<i>Scaled image</i><br>
<img src="//developers.google.com/kml/documentation/images/googleSample.png" width=100><br>
<hr>
Simple Tables:<br>
<table border="1" padding="1">
<tr><td>1</td><td>2</td><td>3</td><td>4</td><td>5</td></tr>
<tr><td>a</td><td>b</td><td>c</td><td>d</td><td>e</td></tr>
</table>
<br>
[Did you notice that double-clicking on the placemark doesn't cause the viewer to take you anywhere? This is because it is possible to directly author a "placeless placemark". If you look at the code for this example, you will see that it has neither a point coordinate nor a LookAt element.]]]></description>
</Placemark>
</Folder>
<Folder>
<name>Ground Overlays</name>
<visibility>0</visibility>
<description>Examples of ground overlays</description>
<GroundOverlay>
<name>Large-scale overlay on terrain</name>
<visibility>0</visibility>
<description>Overlay shows Mount Etna erupting on July 13th, 2001.</description>
<LookAt>
<longitude>15.02468937557116</longitude>
<latitude>37.67395167941667</latitude>
<altitude>0</altitude>
<heading>-16.5581842842829</heading>
<tilt>58.31228652890705</tilt>
<range>30350.36838438907</range>
</LookAt>
<Icon>
<href>http://developers.google.com/kml/documentation/images/etna.jpg</href>
</Icon>
<LatLonBox>
<north>37.91904192681665</north>
<south>37.46543388598137</south>
<east>15.35832653742206</east>
<west>14.60128369746704</west>
<rotation>-0.1556640799496235</rotation>
</LatLonBox>
</GroundOverlay>
</Folder>
<Folder>
<name>Screen Overlays</name>
<visibility>0</visibility>
<description>Screen overlays have to be authored directly in KML. These
examples illustrate absolute and dynamic positioning in screen space.</description>
<ScreenOverlay>
<name>Simple crosshairs</name>
<visibility>0</visibility>
<description>This screen overlay uses fractional positioning to put the
image in the exact center of the screen</description>
<Icon>
<href>http://developers.google.com/kml/documentation/images/crosshairs.png</href>
</Icon>
<overlayXY x="0.5" y="0.5" xunits="fraction" yunits="fraction"/>
<screenXY x="0.5" y="0.5" xunits="fraction" yunits="fraction"/>
<rotationXY x="0.5" y="0.5" xunits="fraction" yunits="fraction"/>
<size x="0" y="0" xunits="pixels" yunits="pixels"/>
</ScreenOverlay>
<ScreenOverlay>
<name>Absolute Positioning: Top left</name>
<visibility>0</visibility>
<Icon>
<href>http://developers.google.com/kml/documentation/images/top_left.jpg</href>
</Icon>
<overlayXY x="0" y="1" xunits="fraction" yunits="fraction"/>
<screenXY x="0" y="1" xunits="fraction" yunits="fraction"/>
<rotationXY x="0" y="0" xunits="fraction" yunits="fraction"/>
<size x="0" y="0" xunits="fraction" yunits="fraction"/>
</ScreenOverlay>
<ScreenOverlay>
<name>Absolute Positioning: Top right</name>
<visibility>0</visibility>
<Icon>
<href>http://developers.google.com/kml/documentation/images/top_right.jpg</href>
</Icon>
<overlayXY x="1" y="1" xunits="fraction" yunits="fraction"/>
<screenXY x="1" y="1" xunits="fraction" yunits="fraction"/>
<rotationXY x="0" y="0" xunits="fraction" yunits="fraction"/>
<size x="0" y="0" xunits="fraction" yunits="fraction"/>
</ScreenOverlay>
<ScreenOverlay>
<name>Absolute Positioning: Bottom left</name>
<visibility>0</visibility>
<Icon>
<href>http://developers.google.com/kml/documentation/images/bottom_left.jpg</href>
</Icon>
<overlayXY x="0" y="-1" xunits="fraction" yunits="fraction"/>
<screenXY x="0" y="0" xunits="fraction" yunits="fraction"/>
<rotationXY x="0" y="0" xunits="fraction" yunits="fraction"/>
<size x="0" y="0" xunits="fraction" yunits="fraction"/>
</ScreenOverlay>
<ScreenOverlay>
<name>Absolute Positioning: Bottom right</name>
<visibility>0</visibility>
<Icon>
<href>http://developers.google.com/kml/documentation/images/bottom_right.jpg</href>
</Icon>
<overlayXY x="1" y="-1" xunits="fraction" yunits="fraction"/>
<screenXY x="1" y="0" xunits="fraction" yunits="fraction"/>
<rotationXY x="0" y="0" xunits="fraction" yunits="fraction"/>
<size x="0" y="0" xunits="fraction" yunits="fraction"/>
</ScreenOverlay>
<ScreenOverlay>
<name>Dynamic Positioning: Top of screen</name>
<visibility>0</visibility>
<Icon>
<href>http://developers.google.com/kml/documentation/images/dynamic_screenoverlay.jpg</href>
</Icon>
<overlayXY x="0" y="1" xunits="fraction" yunits="fraction"/>
<screenXY x="0" y="1" xunits="fraction" yunits="fraction"/>
<rotationXY x="0" y="0" xunits="fraction" yunits="fraction"/>
<size x="1" y="0.2" xunits="fraction" yunits="fraction"/>
</ScreenOverlay>
<ScreenOverlay>
<name>Dynamic Positioning: Right of screen</name>
<visibility>0</visibility>
<Icon>
<href>http://developers.google.com/kml/documentation/images/dynamic_right.jpg</href>
</Icon>
<overlayXY x="1" y="1" xunits="fraction" yunits="fraction"/>
<screenXY x="1" y="1" xunits="fraction" yunits="fraction"/>
<rotationXY x="0" y="0" xunits="fraction" yunits="fraction"/>
<size x="0" y="1" xunits="fraction" yunits="fraction"/>
</ScreenOverlay>
</Folder>
<Folder>
<name>Paths</name>
<visibility>0</visibility>
<description>Examples of paths. Note that the tessellate tag is by default
set to 0. If you want to create tessellated lines, they must be authored
(or edited) directly in KML.</description>
<Placemark>
<name>Tessellated</name>
<visibility>0</visibility>
<description><![CDATA[If the <tessellate> tag has a value of 1, the line will contour to the underlying terrain]]></description>
<LookAt>
<longitude>-112.0822680013139</longitude>
<latitude>36.09825589333556</latitude>
<altitude>0</altitude>
<heading>103.8120432044965</heading>
<tilt>62.04855796276328</tilt>
<range>2889.145007690472</range>
</LookAt>
<LineString>
<tessellate>1</tessellate>
<coordinates> -112.0814237830345,36.10677870477137,0
-112.0870267752693,36.0905099328766,0 </coordinates>
</LineString>
</Placemark>
<Placemark>
<name>Untessellated</name>
<visibility>0</visibility>
<description><![CDATA[If the <tessellate> tag has a value of 0, the line follow a simple straight-line path from point to point]]></description>
<LookAt>
<longitude>-112.0822680013139</longitude>
<latitude>36.09825589333556</latitude>
<altitude>0</altitude>
<heading>103.8120432044965</heading>
<tilt>62.04855796276328</tilt>
<range>2889.145007690472</range>
</LookAt>
<LineString>
<tessellate>0</tessellate>
<coordinates> -112.080622229595,36.10673460007995,0
-112.085242575315,36.09049598612422,0 </coordinates>
</LineString>
</Placemark>
<Placemark>
<name>Absolute</name>
<visibility>0</visibility>
<description>Transparent purple line</description>
<LookAt>
<longitude>-112.2719329043177</longitude>
<latitude>36.08890633450894</latitude>
<altitude>0</altitude>
<heading>-106.8161545998597</heading>
<tilt>44.60763714063257</tilt>
<range>2569.386744398339</range>
</LookAt>
<styleUrl>#transPurpleLineGreenPoly</styleUrl>
<LineString>
<tessellate>1</tessellate>
<altitudeMode>absolute</altitudeMode>
<coordinates> -112.265654928602,36.09447672602546,2357
-112.2660384528238,36.09342608838671,2357
-112.2668139013453,36.09251058776881,2357
-112.2677826834445,36.09189827357996,2357
-112.2688557510952,36.0913137941187,2357
-112.2694810717219,36.0903677207521,2357
-112.2695268555611,36.08932171487285,2357
-112.2690144567276,36.08850916060472,2357
-112.2681528815339,36.08753813597956,2357
-112.2670588176031,36.08682685262568,2357
-112.2657374587321,36.08646312301303,2357 </coordinates>
</LineString>
</Placemark>
<Placemark>
<name>Absolute Extruded</name>
<visibility>0</visibility>
<description>Transparent green wall with yellow outlines</description>
<LookAt>
<longitude>-112.2643334742529</longitude>
<latitude>36.08563154742419</latitude>
<altitude>0</altitude>
<heading>-125.7518698668815</heading>
<tilt>44.61038665812578</tilt>
<range>4451.842204068102</range>
</LookAt>
<styleUrl>#yellowLineGreenPoly</styleUrl>
<LineString>
<extrude>1</extrude>
<tessellate>1</tessellate>
<altitudeMode>absolute</altitudeMode>
<coordinates> -112.2550785337791,36.07954952145647,2357
-112.2549277039738,36.08117083492122,2357
-112.2552505069063,36.08260761307279,2357
-112.2564540158376,36.08395660588506,2357
-112.2580238976449,36.08511401044813,2357
-112.2595218489022,36.08584355239394,2357
-112.2608216347552,36.08612634548589,2357
-112.262073428656,36.08626019085147,2357
-112.2633204928495,36.08621519860091,2357
-112.2644963846444,36.08627897945274,2357
-112.2656969554589,36.08649599090644,2357 </coordinates>
</LineString>
</Placemark>
<Placemark>
<name>Relative</name>
<visibility>0</visibility>
<description>Black line (10 pixels wide), height tracks terrain</description>
<LookAt>
<longitude>-112.2580438551384</longitude>
<latitude>36.1072674824385</latitude>
<altitude>0</altitude>
<heading>4.947421249553717</heading>
<tilt>44.61324882043339</tilt>
<range>2927.61105910266</range>
</LookAt>
<styleUrl>#thickBlackLine</styleUrl>
<LineString>
<tessellate>1</tessellate>
<altitudeMode>relativeToGround</altitudeMode>
<coordinates> -112.2532845153347,36.09886943729116,645
-112.2540466121145,36.09919570465255,645
-112.254734666947,36.09984998366178,645
-112.255493345654,36.10051310621746,645
-112.2563157098468,36.10108441943419,645
-112.2568033076439,36.10159722088088,645
-112.257494011321,36.10204323542867,645
-112.2584106072308,36.10229131995655,645
-112.2596588987972,36.10240001286358,645
-112.2610581199487,36.10213176873407,645
-112.2626285262793,36.10157011437219,645 </coordinates>
</LineString>
</Placemark>
<Placemark>
<name>Relative Extruded</name>
<visibility>0</visibility>
<description>Opaque blue walls with red outline, height tracks terrain</description>
<LookAt>
<longitude>-112.2683594333433</longitude>
<latitude>36.09884362144909</latitude>
<altitude>0</altitude>
<heading>-72.24271551768405</heading>
<tilt>44.60855445139561</tilt>
<range>2184.193522571467</range>
</LookAt>
<styleUrl>#redLineBluePoly</styleUrl>
<LineString>
<extrude>1</extrude>
<tessellate>1</tessellate>
<altitudeMode>relativeToGround</altitudeMode>
<coordinates> -112.2656634181359,36.09445214722695,630
-112.2652238941097,36.09520916122063,630
-112.2645079986395,36.09580763864907,630
-112.2638827428817,36.09628572284063,630
-112.2635746835406,36.09679275951239,630
-112.2635711822407,36.09740038871899,630
-112.2640296531825,36.09804913435539,630
-112.264327720538,36.09880337400301,630
-112.2642436562271,36.09963644790288,630
-112.2639148687042,36.10055381117246,630
-112.2626894973474,36.10149062823369,630 </coordinates>
</LineString>
</Placemark>
</Folder>
<Folder>
<name>Polygons</name>
<visibility>0</visibility>
<description>Examples of polygon shapes</description>
<Folder>
<name>Google Campus</name>
<visibility>0</visibility>
<description>A collection showing how easy it is to create 3-dimensional
buildings</description>
<LookAt>
<longitude>-122.084120030116</longitude>
<latitude>37.42174011925477</latitude>
<altitude>0</altitude>
<heading>-34.82469740081282</heading>
<tilt>53.454348562403</tilt>
<range>276.7870053764046</range>
</LookAt>
<Placemark>
<name>Building 40</name>
<visibility>0</visibility>
<styleUrl>#transRedPoly</styleUrl>
<Polygon>
<extrude>1</extrude>
<altitudeMode>relativeToGround</altitudeMode>
<outerBoundaryIs>
<LinearRing>
<coordinates> -122.0848938459612,37.42257124044786,17
-122.0849580979198,37.42211922626856,17
-122.0847469573047,37.42207183952619,17
-122.0845725380962,37.42209006729676,17
-122.0845954886723,37.42215932700895,17
-122.0838521118269,37.42227278564371,17
-122.083792243335,37.42203539112084,17
-122.0835076656616,37.42209006957106,17
-122.0834709464152,37.42200987395161,17
-122.0831221085748,37.4221046494946,17
-122.0829247374572,37.42226503990386,17
-122.0829339169385,37.42231242843094,17
-122.0833837359737,37.42225046087618,17
-122.0833607854248,37.42234159228745,17
-122.0834204551642,37.42237075460644,17
-122.083659133885,37.42251292011001,17
-122.0839758438952,37.42265873093781,17
-122.0842374743331,37.42265143972521,17
-122.0845036949503,37.4226514386435,17
-122.0848020460801,37.42261133916315,17
-122.0847882750515,37.42256395055121,17
-122.0848938459612,37.42257124044786,17 </coordinates>
</LinearRing>
</outerBoundaryIs>
</Polygon>
</Placemark>
<Placemark>
<name>Building 41</name>
<visibility>0</visibility>
<styleUrl>#transBluePoly</styleUrl>
<Polygon>
<extrude>1</extrude>
<altitudeMode>relativeToGround</altitudeMode>
<outerBoundaryIs>
<LinearRing>
<coordinates> -122.0857412771483,37.42227033155257,17
-122.0858169768481,37.42231408832346,17
-122.085852582875,37.42230337469744,17
-122.0858799945639,37.42225686138789,17
-122.0858860101409,37.4222311076138,17
-122.0858069157288,37.42220250173855,17
-122.0858379542653,37.42214027058678,17
-122.0856732640519,37.42208690214408,17
-122.0856022926407,37.42214885429042,17
-122.0855902778436,37.422128290487,17
-122.0855841672237,37.42208171967246,17
-122.0854852065741,37.42210455874995,17
-122.0855067264352,37.42214267949824,17
-122.0854430712915,37.42212783846172,17
-122.0850990714904,37.42251282407603,17
-122.0856769818632,37.42281815323651,17
-122.0860162273783,37.42244918858722,17
-122.0857260327004,37.42229239604253,17
-122.0857412771483,37.42227033155257,17 </coordinates>
</LinearRing>
</outerBoundaryIs>
</Polygon>
</Placemark>
<Placemark>
<name>Building 42</name>
<visibility>0</visibility>
<styleUrl>#transGreenPoly</styleUrl>
<Polygon>
<extrude>1</extrude>
<altitudeMode>relativeToGround</altitudeMode>
<outerBoundaryIs>
<LinearRing>
<coordinates> -122.0857862287242,37.42136208886969,25
-122.0857312990603,37.42136935989481,25
-122.0857312992918,37.42140934910903,25
-122.0856077073679,37.42138390166565,25
-122.0855802426516,37.42137299550869,25
-122.0852186221971,37.42137299504316,25
-122.0852277765639,37.42161656508265,25
-122.0852598189347,37.42160565894403,25
-122.0852598185499,37.42168200156,25
-122.0852369311478,37.42170017860346,25
-122.0852643957828,37.42176197982575,25
-122.0853239032746,37.42176198013907,25
-122.0853559454324,37.421852864452,25
-122.0854108752463,37.42188921823734,25
-122.0854795379357,37.42189285337048,25
-122.0855436229819,37.42188921797546,25
-122.0856260178042,37.42186013499926,25
-122.085937287963,37.42186013453605,25
-122.0859428718666,37.42160898590042,25
-122.0859655469861,37.42157992759144,25
-122.0858640462341,37.42147115002957,25
-122.0858548911215,37.42140571326184,25
-122.0858091162768,37.4214057134039,25
-122.0857862287242,37.42136208886969,25 </coordinates>
</LinearRing>
</outerBoundaryIs>
</Polygon>
</Placemark>
<Placemark>
<name>Building 43</name>
<visibility>0</visibility>
<styleUrl>#transYellowPoly</styleUrl>
<Polygon>
<extrude>1</extrude>
<altitudeMode>relativeToGround</altitudeMode>
<outerBoundaryIs>
<LinearRing>
<coordinates> -122.0844371128284,37.42177253003091,19
-122.0845118855746,37.42191111542896,19
-122.0850470999805,37.42178755121535,19
-122.0850719913391,37.42143663023161,19
-122.084916406232,37.42137237822116,19
-122.0842193868167,37.42137237801626,19
-122.08421938659,37.42147617161496,19
-122.0838086419991,37.4214613409357,19
-122.0837899728564,37.42131306410796,19
-122.0832796534698,37.42129328840593,19
-122.0832609819207,37.42139213944298,19
-122.0829373621737,37.42137236399876,19
-122.0829062425667,37.42151569778871,19
-122.0828502269665,37.42176282576465,19
-122.0829435788635,37.42176776969635,19
-122.083217411188,37.42179248552686,19
-122.0835970430103,37.4217480074456,19
-122.0839455556771,37.42169364237603,19
-122.0840077894637,37.42176283815853,19
-122.084113587521,37.42174801104392,19
-122.0840762473784,37.42171341292375,19
-122.0841447047739,37.42167881534569,19
-122.084144704223,37.42181720660197,19
-122.0842503333074,37.4218170700446,19
-122.0844371128284,37.42177253003091,19 </coordinates>
</LinearRing>
</outerBoundaryIs>
</Polygon>
</Placemark>
</Folder>
<Folder>
<name>Extruded Polygon</name>
<description>A simple way to model a building</description>
<Placemark>
<name>The Pentagon</name>
<LookAt>
<longitude>-77.05580139178142</longitude>
<latitude>38.870832443487</latitude>
<heading>59.88865561738225</heading>
<tilt>48.09646074797388</tilt>
<range>742.0552506670548</range>
</LookAt>
<Polygon>
<extrude>1</extrude>
<altitudeMode>relativeToGround</altitudeMode>
<outerBoundaryIs>
<LinearRing>
<coordinates> -77.05788457660967,38.87253259892824,100
-77.05465973756702,38.87291016281703,100
-77.05315536854791,38.87053267794386,100
-77.05552622493516,38.868757801256,100
-77.05844056290393,38.86996206506943,100
-77.05788457660967,38.87253259892824,100 </coordinates>
</LinearRing>
</outerBoundaryIs>
<innerBoundaryIs>
<LinearRing>
<coordinates> -77.05668055019126,38.87154239798456,100
-77.05542625960818,38.87167890344077,100
-77.05485125901024,38.87076535397792,100
-77.05577677433152,38.87008686581446,100
-77.05691162017543,38.87054446963351,100
-77.05668055019126,38.87154239798456,100 </coordinates>
</LinearRing>
</innerBoundaryIs>
</Polygon>
</Placemark>
</Folder>
<Folder>
<name>Absolute and Relative</name>
<visibility>0</visibility>
<description>Four structures whose roofs meet exactly. Turn on/off
terrain to see the difference between relative and absolute
positioning.</description>
<LookAt>
<longitude>-112.3348969157552</longitude>
<latitude>36.14845533214919</latitude>
<altitude>0</altitude>
<heading>-86.91235037566909</heading>
<tilt>49.30695423894192</tilt>
<range>990.6761201087104</range>
</LookAt>
<Placemark>
<name>Absolute</name>
<visibility>0</visibility>
<styleUrl>#transBluePoly</styleUrl>
<Polygon>
<tessellate>1</tessellate>
<altitudeMode>absolute</altitudeMode>
<outerBoundaryIs>
<LinearRing>
<coordinates> -112.3372510731295,36.14888505105317,1784
-112.3356128688403,36.14781540589019,1784
-112.3368169371048,36.14658677734382,1784
-112.3384408457543,36.14762778914076,1784
-112.3372510731295,36.14888505105317,1784 </coordinates>
</LinearRing>
</outerBoundaryIs>
</Polygon>
</Placemark>
<Placemark>
<name>Absolute Extruded</name>
<visibility>0</visibility>
<styleUrl>#transRedPoly</styleUrl>
<Polygon>
<extrude>1</extrude>
<tessellate>1</tessellate>
<altitudeMode>absolute</altitudeMode>
<outerBoundaryIs>
<LinearRing>
<coordinates> -112.3396586818843,36.14637618647505,1784
-112.3380597654315,36.14531751871353,1784
-112.3368254237788,36.14659596244607,1784
-112.3384555043203,36.14762621763982,1784
-112.3396586818843,36.14637618647505,1784 </coordinates>
</LinearRing>
</outerBoundaryIs>
</Polygon>
</Placemark>
<Placemark>
<name>Relative</name>
<visibility>0</visibility>
<LookAt>
<longitude>-112.3350152490417</longitude>
<latitude>36.14943123077423</latitude>
<altitude>0</altitude>
<heading>-118.9214100848499</heading>
<tilt>37.92486261093203</tilt>
<range>345.5169113679813</range>
</LookAt>
<styleUrl>#transGreenPoly</styleUrl>
<Polygon>
<tessellate>1</tessellate>
<altitudeMode>relativeToGround</altitudeMode>
<outerBoundaryIs>
<LinearRing>
<coordinates> -112.3349463145932,36.14988705767721,100
-112.3354019540677,36.14941108398372,100
-112.3344428289146,36.14878490381308,100
-112.3331289492913,36.14780840132443,100
-112.3317019516947,36.14680755678357,100
-112.331131440106,36.1474173426228,100
-112.332616324338,36.14845453364654,100
-112.3339876620524,36.14926570522069,100
-112.3349463145932,36.14988705767721,100 </coordinates>
</LinearRing>
</outerBoundaryIs>
</Polygon>
</Placemark>
<Placemark>
<name>Relative Extruded</name>
<visibility>0</visibility>
<LookAt>
<longitude>-112.3351587892382</longitude>
<latitude>36.14979247129029</latitude>
<altitude>0</altitude>
<heading>-55.42811560891606</heading>
<tilt>56.10280503739589</tilt>
<range>401.0997279712519</range>
</LookAt>
<styleUrl>#transYellowPoly</styleUrl>
<Polygon>
<extrude>1</extrude>
<tessellate>1</tessellate>
<altitudeMode>relativeToGround</altitudeMode>
<outerBoundaryIs>
<LinearRing>
<coordinates> -112.3348783983763,36.1514008468736,100
-112.3372535345629,36.14888517553886,100
-112.3356068927954,36.14781612679284,100
-112.3350034807972,36.14846469024177,100
-112.3358353861232,36.1489624162954,100
-112.3345888301373,36.15026229372507,100
-112.3337937856278,36.14978096026463,100
-112.3331798208424,36.1504472788618,100
-112.3348783983763,36.1514008468736,100 </coordinates>
</LinearRing>
</outerBoundaryIs>
</Polygon>
</Placemark>
</Folder>
</Folder>
</Document>
</kml>

View file

@ -0,0 +1,19 @@
<html>
<head>
<title>Leaflet</title>
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7/leaflet.css" />
<script src="http://cdn.leafletjs.com/leaflet-0.7/leaflet.js"></script>
<script src="../layer/tile/Bing.js"></script>
</head>
<body>
<div style="width:100%; height:100%" id="map"></div>
<script type='text/javascript'>
var map = new L.Map('map', {center: new L.LatLng(67.6755, 33.936), zoom: 10 });
var osm = new L.TileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png');
var bing = new L.BingLayer("Anqm0F_JjIZvT0P3abS6KONpaBaKuTnITRrnYuiJCE0WOhH6ZbE4DzeT6brvKVR5");
map.addLayer(bing);
map.addControl(new L.Control.Layers({'OSM':osm, "Bing":bing}, {}));
</script>
</body>
</html>

View file

@ -0,0 +1,54 @@
<html>
<head>
<title>Leaflet</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7/leaflet.css" />
<script src="http://cdn.leafletjs.com/leaflet-0.7/leaflet.js"></script>
<!--<script src="http://api-maps.yandex.ru/2.0/?load=package.map&lang=ru-RU" type="text/javascript"></script>-->
<script src="../layer/Layer.Deferred.js"></script>
<!--<script src="../layer/tile/Yandex.js"></script>-->
</head>
<body>
<div style="width:100%; height:100%" id="map"></div>
<script type='text/javascript'>
var map = new L.Map('map', {center: new L.LatLng(67.6755, 33.936), zoom: 10, zoomAnimation: false });
var osm = new L.TileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);
var layerdefs = {
mapnik: { name: "Mapnik", js: [],
init: function() {return new L.TileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png');}},
kosmo: { name: "Космоснимки", js: [],
init: function() {return new L.TileLayer('http://{s}.tile.osmosnimki.ru/kosmo/{z}/{x}/{y}.png', {attribution:'Tiles Courtesy of <a href="http://kosmosnimki.ru/" target="_blank">kosmosnimki.ru</a>'});}},
gsat: { name: "Google", js: ["../layer/tile/Google.js", "http://maps.google.com/maps/api/js?v=3&sensor=false&callback=L.Google.asyncInitialize"],
init: function() {return new L.Google(); }},
ysat: { name: "Yandex", js: ["../layer/tile/Yandex.js", "http://api-maps.yandex.ru/2.0/?load=package.map&lang=ru-RU"],
init: function() {return new L.Yandex("satellite"); }},
nyak: { name: "НЯК", js: ["../layer/tile/Yandex.js", "http://api-maps.yandex.ru/2.0/?load=package.map&lang=ru-RU"],
init: function() {return new L.Yandex("publicMap"); }},
traffic: { name: "Пробки", js: ["../layer/tile/Yandex.js", "http://api-maps.yandex.ru/2.0/?load=package.map&lang=ru-RU"],
init: function() {return new L.Yandex("null", {traffic: true, opacity: 0.8, overlay: true}); }, overlay: true},
mso: { name: "Mapsurfer", js: [], overlay: true,
init: function() {return new L.TileLayer('http://129.206.74.245:8003/tms_h.ashx?x={x}&y={y}&z={z}');}}
};
var yndx = new L.DeferredLayer(layerdefs.ysat);
var kosmo = new L.DeferredLayer(layerdefs.kosmo);
var google = new L.DeferredLayer(layerdefs.gsat);
var ytraffic = new L.DeferredLayer(layerdefs.traffic);
var mso = new L.DeferredLayer(layerdefs.mso);
L.control.layers(
{
'OSM':osm,
"Kosmo":kosmo,
"Google":google,
"Yandex":yndx
},{
"Пробки":ytraffic,
"OpenMapSurfer":mso
}
).addTo(map);
</script>
</body>
</html>

View file

@ -0,0 +1,36 @@
<html>
<!--
vim: sts=4 sw=4 et
-->
<head>
<title>Leaflet</title>
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7/leaflet.css" />
<link rel="stylesheet" href="../css/distance.css" />
<link rel="stylesheet" href="http://leaflet.github.io/Leaflet.draw/leaflet.draw.css" />
<script src="http://cdn.leafletjs.com/leaflet-0.7/leaflet.js"></script>
<script src="http://leaflet.github.io/Leaflet.draw/leaflet.draw.js"></script>
<script src="../layer/vector/GPX.js"></script>
<script src="../control/Scale.js"></script>
<script src="../control/Permalink.js"></script>
<script src="../control/Permalink.Line.js"></script>
<script src="../control/Distance.js"></script>
</head>
</head>
<body>
<!-- define a DIV into which the map will appear. Make it take up the whole window -->
<div style="width:100%; height:100%" id="map"></div>
<script type='text/javascript'>
</script>
<script type="text/javascript">
var map = new L.Map('map', {center: new L.LatLng(58.4, 43.0), zoom: 11});
var osm = new L.TileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png');
map.addLayer(osm);
var d = new L.Control.Distance(); map.addControl(d);
map.addControl(new L.Control.Scale());
map.addControl(new L.Control.Permalink({line: d.getLine(), useLocation: true}));
</script>
</body>
</html>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,33 @@
<html>
<head>
<title>Google Custom Styles // Leaflet</title>
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7/leaflet.css" />
<script src="http://cdn.leafletjs.com/leaflet-0.7/leaflet.js"></script>
<script src="http://maps.google.com/maps/api/js?v=3&sensor=false"></script>
<script src="../layer/tile/Google.js"></script>
</head>
<body>
<!-- define a DIV into which the map will appear. Make it take up the whole window -->
<div style="width:100%; height:100%" id="map"></div>
<script type='text/javascript'>
var map = new L.Map('map', {center: new L.LatLng(53.9618, 58.4277), zoom: 13});
var osm = new L.TileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png');
var styles = [
{
featureType: 'all',
stylers: [{hue: '#ff0000'}]
}
];
var ggl = new L.Google('ROADMAP', {
mapOptions: {
styles: styles
}
});
map.addLayer(ggl);
map.addControl(new L.Control.Layers( {'OSM':osm, 'Google':ggl}, {}));
</script>
</body>
</html>

View file

@ -0,0 +1,22 @@
<html>
<head>
<title>Google // Leaflet</title>
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7/leaflet.css" />
<script src="http://cdn.leafletjs.com/leaflet-0.7/leaflet.js"></script>
<script src="http://maps.google.com/maps/api/js?v=3&sensor=false"></script>
<script src="../layer/tile/Google.js"></script>
</head>
<body>
<!-- define a DIV into which the map will appear. Make it take up the whole window -->
<div style="width:100%; height:100%" id="map"></div>
<script type='text/javascript'>
var map = new L.Map('map', {center: new L.LatLng(53.9618, 58.4277), zoom: 13});
var osm = new L.TileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png');
var ggl = new L.Google();
var ggl2 = new L.Google('TERRAIN');
map.addLayer(ggl);
map.addControl(new L.Control.Layers( {'OSM':osm, 'Google':ggl, 'Google Terrain':ggl2}, {}));
</script>
</body>
</html>

View file

@ -0,0 +1,24 @@
<html>
<head>
<title>Leaflet - GPX track without Waypoints</title>
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7/leaflet.css" />
<script src="http://cdn.leafletjs.com/leaflet-0.7/leaflet.js"></script>
<script src="../layer/vector/GPX.js"></script>
</head>
<body>
<!-- define a DIV into which the map will appear. Make it take up the whole window -->
<div style="width:100%; height:100%" id="map"></div>
<script type='text/javascript'>
var map = new L.Map('map', {center: new L.LatLng(58.4, 43.0), zoom: 11});
var osm = new L.TileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png');
var track = new L.GPX("fells_loop.gpx", {async: true, display_wpt:false})
.on("loaded", function(e) { map.fitBounds(e.target.getBounds()); });
map.addLayer(track);
map.addLayer(osm);
map.addControl(new L.Control.Layers({}, {'GPX':track}));
</script>
</body>
</html>

View file

@ -0,0 +1,26 @@
<html>
<head>
<title>Leaflet</title>
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7/leaflet.css" />
<script src="http://cdn.leafletjs.com/leaflet-0.7/leaflet.js"></script>
<script src="../layer/vector/GPX.js"></script>
<script src="../layer/vector/GPX.Speed.js"></script>
</head>
<body>
<!-- define a DIV into which the map will appear. Make it take up the whole window -->
<div style="width:100%; height:100%" id="map"></div>
<script type='text/javascript'>
var map = new L.Map('map', {center: new L.LatLng(58.4, 43.0), zoom: 11});
var osm = new L.TileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png');
var track = new L.GPX("speed.gpx", {async: true})
.on("loaded", function(e) { map.fitBounds(e.target.getBounds()); })
.speedSplitEnable({maxSpeed: 100, chunks: 1000});
map.addLayer(track);
map.addLayer(osm);
map.addControl(new L.Control.Layers({}, {'GPX':track}));
</script>
</body>
</html>

View file

@ -0,0 +1,24 @@
<html>
<head>
<title>Leaflet</title>
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7/leaflet.css" />
<script src="http://cdn.leafletjs.com/leaflet-0.7/leaflet.js"></script>
<script src="../layer/vector/GPX.js"></script>
</head>
<body>
<!-- define a DIV into which the map will appear. Make it take up the whole window -->
<div style="width:100%; height:100%" id="map"></div>
<script type='text/javascript'>
var map = new L.Map('map', {center: new L.LatLng(58.4, 43.0), zoom: 11});
var osm = new L.TileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png');
var track = new L.GPX("fells_loop.gpx", {async: true})
.on("loaded", function(e) { map.fitBounds(e.target.getBounds()); });
map.addLayer(track);
map.addLayer(osm);
map.addControl(new L.Control.Layers({}, {'GPX':track}));
</script>
</body>
</html>

View file

@ -0,0 +1,24 @@
<html>
<head>
<title>Leaflet</title>
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7/leaflet.css" />
<script src="http://cdn.leafletjs.com/leaflet-0.7/leaflet.js"></script>
<script src="../layer/vector/KML.js"></script>
</head>
<body>
<!-- define a DIV into which the map will appear. Make it take up the whole window -->
<div style="width:100%; height:100%" id="map"></div>
<script type='text/javascript'>
var map = new L.Map('map', {center: new L.LatLng(58.4, 43.0), zoom: 11});
var osm = new L.TileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png');
var track = new L.KML("KML_Samples.kml", {async: true});
track.on("loaded", function(e) { map.fitBounds(e.target.getBounds()); });
map.addLayer(track);
map.addLayer(osm);
map.addControl(new L.Control.Layers({}, {'Track':track}));
</script>
</body>
</html>

View file

@ -0,0 +1,39 @@
<html>
<head>
<title>Leaflet</title>
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7/leaflet.css" />
<script src="http://cdn.leafletjs.com/leaflet-0.7/leaflet.js"></script>
<script src="../layer/Icon.Canvas.js"></script>
</head>
<body>
<div style="width:100%; height:100%" id="map"></div>
<script type='text/javascript'>
var map = new L.Map('map', {center: new L.LatLng(67.66904, 33.68595), zoom: 10});
map.addLayer(new L.TileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'));
var circle = new L.Icon.Canvas({iconSize: new L.Point(30, 30)});
circle.draw = function(ctx, w, h) {
ctx.translate(w/2, h/2);
ctx.beginPath();
ctx.fillStyle = "#F00";
ctx.arc(0, 0, w/2-1, 0, Math.PI*2, true);
ctx.fill();
ctx.lineWidth = 2;
ctx.strokeStyle = '#FFF';
ctx.moveTo(-w/5, -h/5);
ctx.lineTo(w/5, h/5);
ctx.moveTo(-w/5, h/5);
ctx.lineTo(w/5, -h/5);
ctx.stroke();
ctx.closePath();
}
map.addLayer(new L.Marker(map.getCenter(), {icon: circle, draggable: true, opacity: 0.7}));
</script>
</body>
</html>

View file

@ -0,0 +1,27 @@
<html>
<head>
<title>Leaflet</title>
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7/leaflet.css" />
<script src="http://cdn.leafletjs.com/leaflet-0.7/leaflet.js"></script>
<script src="../layer/Marker.Rotate.js"></script>
</head>
<body>
<div style="width:100%; height:100%" id="map"></div>
<script type='text/javascript'>
var map = new L.Map('map', {center: new L.LatLng(67.6755, 33.936), zoom: 10});
map.addLayer(new L.TileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'));
var marker = new L.Marker(map.getCenter(), {iconAngle: 90});
map.addLayer(marker);
var angle = 0;
function _rotate() {
marker.setIconAngle(angle);
angle = (angle + 10) % 360;
setTimeout(_rotate, 1000);
}
_rotate();
</script>
</body>
</html>

View file

@ -0,0 +1,21 @@
<html>
<head>
<title>Leaflet</title>
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7/leaflet.css" />
<script src="http://cdn.leafletjs.com/leaflet-0.7/leaflet.js"></script>
<script src="../layer/Marker.Text.js"></script>
</head>
<body>
<div style="width:100%; height:100%" id="map"></div>
<script type='text/javascript'>
var map = new L.Map('map', {center: new L.LatLng(67.66904, 33.68595), zoom: 10});
map.addLayer(new L.TileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'));
var marker = new L.Marker.Text(map.getCenter(), 'Sweet!');
map.addLayer(new L.Marker(map.getCenter(), {opacity: 0.5}));
map.addLayer(marker);
</script>
</body>
</html>

View file

@ -0,0 +1,24 @@
<html>
<!--
vim: sts=4 sw=4 et
-->
<head>
<title>Leaflet</title>
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7/leaflet.css" />
<link rel="stylesheet" href="../css/osb.css" />
<script src="http://cdn.leafletjs.com/leaflet-0.7/leaflet.js"></script>
<script src="../layer/OpenStreetBugs.js"></script>
</head>
</head>
<body>
<div style="width:100%; height:100%" id="map"></div>
<script type="text/javascript">
var map = new L.Map('map', {center: new L.LatLng(57.62, 39.89), zoom: 11});
map.addLayer(new L.TileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'));
map.addLayer(new L.OpenStreetBugs());
</script>
</body>
</html>

View file

@ -0,0 +1,21 @@
<html>
<head>
<title>Leaflet</title>
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7/leaflet.css" />
<script src="http://cdn.leafletjs.com/leaflet-0.7/leaflet.js"></script>
<script src="../layer/vector/OSM.js"></script>
</head>
<body>
<div style="width:100%; height:100%" id="map"></div>
<script type='text/javascript'>
var map = new L.Map('map', {center: new L.LatLng(55.7, 37.6), zoom: 9, zoomAnimation: false });
map.addLayer(new L.TileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'));
var layer = new L.OSM('ulugo.osm')
.on("loaded", function(e) { map.fitBounds(e.target.getBounds()); });
map.addLayer(layer);
map.addControl(new L.Control.Layers({}, {"OSM":layer}));
</script>
</body>
</html>

View file

@ -0,0 +1,23 @@
<html>
<head>
<title>Leaflet</title>
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7/leaflet.css" />
<script src="http://cdn.leafletjs.com/leaflet-0.7/leaflet.js"></script>
<script src="../control/Permalink.js"></script>
<script src="../control/Permalink.Marker.js"></script>
<script src="../control/Permalink.Layer.js"></script>
</head>
<body>
<div style="width:100%; height:100%" id="map"></div>
<script type='text/javascript'>
var map = new L.Map('map', {center: new L.LatLng(67.6755, 33.936), zoom: 10});
var osm = new L.TileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png');
map.addLayer(osm);
var kosm = new L.TileLayer('http://{s}.tile.osmosnimki.ru/kosmo/{z}/{x}/{y}.png', {attribution:'Tiles Courtesy of <a href="http://kosmosnimki.ru/" target="_blank">kosmosnimki.ru</a>'});
var layers = new L.Control.Layers({'OSM':osm, "Kosmo":kosm});
map.addControl(layers);
map.addControl(new L.Control.Permalink({text: 'Permalink', layers: layers}));
</script>
</body>
</html>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,24 @@
<html>
<head>
<title>Leaflet</title>
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7/leaflet.css" />
<script src="http://cdn.leafletjs.com/leaflet-0.7/leaflet.js"></script>
<script src="http://api-maps.yandex.ru/2.0/?load=package.map&lang=ru-RU" type="text/javascript"></script>
<script src="../layer/tile/Yandex.js"></script>
</head>
<body>
<div style="width:100%; height:100%" id="map"></div>
<script type='text/javascript'>
var map = new L.Map('map', {center: new L.LatLng(67.6755, 33.936), zoom: 10, zoomAnimation: false });
var osm = new L.TileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png');
var yndx = new L.Yandex();
var ytraffic = new L.Yandex("null", {traffic:true, opacity:0.8, overlay:true});
map.addLayer(osm);
map.addLayer(ytraffic);
map.addControl(new L.Control.Layers({'OSM':osm, "Yandex":yndx},
{"Traffic":ytraffic}));
</script>
</body>
</html>

View file

@ -0,0 +1,27 @@
L.Icon.Canvas = L.Icon.extend({
options: {
iconSize: new L.Point(20, 20), // Have to be supplied
/*
iconAnchor: (Point)
popupAnchor: (Point)
*/
className: 'leaflet-canvas-icon'
},
createIcon: function () {
var e = document.createElement('canvas');
this._setIconStyles(e, 'icon');
var s = this.options.iconSize;
e.width = s.x
e.height = s.y;
this.draw(e.getContext('2d'), s.x, s.y);
return e;
},
createShadow: function () {
return null;
},
draw: function(canvas, width, height) {
}
});

View file

@ -0,0 +1,57 @@
L.DeferredLayer = L.LayerGroup.extend({
options: {
js: [],
init: null
},
_script_cache: {},
initialize: function(options) {
L.Util.setOptions(this, options);
L.LayerGroup.prototype.initialize.apply(this);
this._loaded = false;
},
onAdd: function(map) {
L.LayerGroup.prototype.onAdd.apply(this, [map]);
if (this._loaded) return;
//console.info("Script cache", this._script_cache);
var loaded = function() {
//console.info("Loaded", this, this.options);
this._loaded = true;
var l = this.options.init();
if (l)
this.addLayer(l);
}
this._loadScripts(this.options.js.reverse(), L.Util.bind(loaded, this));
},
_loadScripts: function(scripts, cb, args) {
if (!scripts || scripts.length == 0)
return cb(args);
var _this = this, s = scripts.pop(), c;
c = this._script_cache[s];
if (c === undefined) {
c = {url: s, wait: []};
//console.info("Load ", s);
var script = document.createElement('script');
script.src = s;
script.type = 'text/javascript';
script.onload = function () {
//console.info("Element(cb)", c.e.readyState);
c.e.readyState = "completed";
var i = 0;
for (i = 0; i < c.wait.length; i++)
c.wait[i]();
}
c.e = script;
document.getElementsByTagName('head')[0].appendChild(script);
}
function _cb() { _this._loadScripts(scripts, cb, args); }
c.wait.push(_cb);
//console.info("Element", c.e.readyState);
if (c.e.readyState == "completed")
_cb();
this._script_cache[s] = c;
}
});

View file

@ -0,0 +1,53 @@
/*
* Based on comments by @runanet and @coomsie
* https://github.com/CloudMade/Leaflet/issues/386
*
* Wrapping function is needed to preserve L.Marker.update function
*/
(function () {
var _old__setPos = L.Marker.prototype._setPos;
L.Marker.include({
_updateImg: function(i, a, s) {
a = L.point(s).divideBy(2)._subtract(L.point(a));
var transform = '';
transform += ' translate(' + -a.x + 'px, ' + -a.y + 'px)';
transform += ' rotate(' + this.options.iconAngle + 'deg)';
transform += ' translate(' + a.x + 'px, ' + a.y + 'px)';
i.style[L.DomUtil.TRANSFORM] += transform;
},
setIconAngle: function (iconAngle) {
this.options.iconAngle = iconAngle;
if (this._map) this.update();
},
_setPos: function (pos) {
if (this._icon) {
this._icon.style[L.DomUtil.TRANSFORM] = "";
}
if (this._shadow) {
this._shadow.style[L.DomUtil.TRANSFORM] = "";
}
_old__setPos.apply(this,[pos]);
if (this.options.iconAngle) {
var a = this.options.icon.options.iconAnchor;
var s = this.options.icon.options.iconSize;
var i;
if (this._icon) {
i = this._icon;
this._updateImg(i, a, s);
}
if (this._shadow) {
// Rotate around the icons anchor.
s = this.options.icon.options.shadowSize;
i = this._shadow;
this._updateImg(i, a, s);
}
} }
});
}());

View file

@ -0,0 +1,51 @@
L.Icon.Text = L.Icon.extend({
initialize: function (text, options) {
this._text = text;
L.Icon.prototype.initialize.apply(this, [options]);
},
createIcon: function() {
var el = document.createElement('div');
el.appendChild(document.createTextNode(this._text));
this._setIconStyles(el, 'icon');
el.style.textShadow = "2px 2px 2px #fff";
return el;
},
createShadow: function() { return null; }
});
L.Marker.Text = L.Marker.extend({
initialize: function (latlng, text, options) {
L.Marker.prototype.initialize.apply(this, [latlng, options]);
this._fakeicon = new L.Icon.Text(text);
},
_initIcon: function() {
L.Marker.prototype._initIcon.apply(this);
var i = this._icon, s = this._shadow, obj = this.options.icon
this._icon = this._shadow = null;
this.options.icon = this._fakeicon;
L.Marker.prototype._initIcon.apply(this);
this.options.icon = obj;
if (s) {
s.parentNode.removeChild(s);
this._icon.appendChild(s);
}
i.parentNode.removeChild(i);
this._icon.appendChild(i);
var w = this._icon.clientWidth, h = this._icon.clientHeight;
this._icon.style.marginLeft = -w / 2 + "px";
//this._icon.style.backgroundColor = "red";
var off = new L.Point(w/2, 0);
if (L.Browser.webkit) off.y = -h;
L.DomUtil.setPosition(i, off);
if (s) L.DomUtil.setPosition(s, off);
}
});

View file

@ -0,0 +1,497 @@
L.OpenStreetBugs = L.FeatureGroup.extend({
options : {
serverURL : "http://openstreetbugs.schokokeks.org/api/0.1/",
readonly : false,
setCookie : true,
username : "NoName",
cookieLifetime : 1000,
cookiePath : null,
permalinkZoom : 14,
permalinkUrl: null,
opacity : 0.7,
showOpen: true,
showClosed: true,
iconOpen: "http://openstreetbugs.schokokeks.org/client/open_bug_marker.png",
iconClosed:"http://openstreetbugs.schokokeks.org/client/closed_bug_marker.png",
iconActive: undefined,
editArea: 0.01,
popupOptions: {autoPan: false},
dblClick: true
},
initialize : function(options)
{
var tmp = L.Util.extend({}, this.options.popupOptions, (options || {}).popupOptions)
L.Util.setOptions(this, options)
this.options.popupOptions = tmp;
putAJAXMarker.layers.push(this);
this.bugs = {};
this._layers = {};
var username = this.get_cookie("osbUsername");
if (username)
this.options.username = username;
L.OpenStreetBugs.setCSS();
},
onAdd : function(map)
{
L.FeatureGroup.prototype.onAdd.apply(this, [map]);
this._map.on("moveend", this.loadBugs, this);
this.loadBugs();
if (!this.options.readonly) {
if (this.options.dblClick) {
map.doubleClickZoom.disable();
map.on('dblclick', this.addBug, this);
}
else {
map.on('click', this.addBug, this);
}
}
this.fire('add');
},
onRemove : function(map)
{
this._map.off("moveend", this.loadBugs, this);
this._iterateLayers(map.removeLayer, map);
delete this._map;
if (!this.options.readonly) {
if (this.options.dblClick) {
map.doubleClickZoom.enable();
map.off('dblclick', this.addBug, this);
}
else {
map.off('click', this.addBug, this);
}
}
this.fire('remove');
},
set_cookie : function(name, value)
{
var expires = (new Date((new Date()).getTime() + 604800000)).toGMTString(); // one week from now
document.cookie = name+"="+escape(value)+";";
},
get_cookie : function(name)
{
var cookies = (document.cookie || '').split(/;\s*/);
for(var i=0; i<cookies.length; i++)
{
var cookie = cookies[i].split("=");
if(cookie[0] == name)
return unescape(cookie[1]);
}
return null;
},
loadBugs : function()
{
//if(!this.getVisibility())
// return true;
var bounds = this._map.getBounds();
if(!bounds) return false;
var sw = bounds.getSouthWest(), ne = bounds.getNorthEast();
function round(number, digits) {
var factor = Math.pow(10, digits);
return Math.round(number*factor)/factor;
}
this.apiRequest("getBugs"
+ "?t="+round(ne.lat, 5)
+ "&r="+round(ne.lng, 5)
+ "&b="+round(sw.lat, 5)
+ "&l="+round(sw.lng, 5));
},
apiRequest : function(url, reload)
{
var script = document.createElement("script");
script.type = "text/javascript";
script.src = this.options.serverURL + url + "&nocache="+(new Date()).getTime();
var _this = this;
script.onload = function(e) {
document.body.removeChild(this);
if (reload) _this.loadBugs();
};
document.body.appendChild(script);
},
createMarker: function(id, force)
{
var bug = putAJAXMarker.bugs[id];
if(this.bugs[id])
{
if (force || this.bugs[id].osb.closed != bug[2])
this.removeLayer(this.bugs[id]);
else
return;
}
var closed = bug[2];
if (closed && !this.options.showClosed) return;
if (!closed && !this.options.showOpen) return;
var icon_url = null;
var class_popup = ' osb';
if (bug[2]) {
icon_url = this.options.iconClosed;
class_popup += ' osbClosed';
}
else if (bug[1].length == 1) {
icon_url = this.options.iconOpen;
class_popup += ' osbOpen';
}
else {
if (this.options.iconActive) {
icon_url = this.options.iconActive;
class_popup += ' osbActive';
}
else {
icon_url = this.options.iconOpen;
class_popup += ' osbOpen';
}
}
var feature = new L.Marker(bug[0], {icon:new this.osbIcon({iconUrl: icon_url})});
feature.osb = {id: id, closed: closed};
this.addLayer(feature);
this.bugs[id] = feature;
this.setPopupContent(id);
feature._popup.options.className += class_popup;
if (this.options.bugid && (parseInt(this.options.bugid) == id))
feature.openPopup();
//this.events.triggerEvent("markerAdded");
},
osbIcon : L.Icon.extend({
options: {
iconUrl: 'http://openstreetbugs.schokokeks.org/client/open_bug_marker.png',
iconSize: new L.Point(22, 22),
shadowSize: new L.Point(0, 0),
iconAnchor: new L.Point(11, 11),
popupAnchor: new L.Point(0, -11)
}
}),
setPopupContent: function(id) {
if(this.bugs[id]._popup_content)
return;
var el1,el2,el3;
var layer = this;
var rawbug = putAJAXMarker.bugs[id];
var isclosed = rawbug[2];
var newContent = L.DomUtil.create('div', 'osb-popup');
var h1 = L.DomUtil.create('h1', null, newContent);
if (rawbug[2])
h1.textContent = L.i18n("Fixed Error");
else if (rawbug[1].length == 1)
h1.textContent = L.i18n("Unresolved Error");
else
h1.textContent = L.i18n("Active Error");
var divinfo = L.DomUtil.create('div', 'osb-info', newContent);
var table = L.DomUtil.create('table', 'osb-table', divinfo);
for(var i=0; i<rawbug[1].length; i++)
{
var tr = L.DomUtil.create('tr', "osb-tr-info", table);
tr.setAttribute("valign","top")
var td = L.DomUtil.create('td', "osb-td-nickname", tr);
td.textContent = rawbug[5][i] + ':';
var td = L.DomUtil.create('td', "osb-td-datetime", tr);
td.textContent = rawbug[6][i];
var td = L.DomUtil.create('td', "osb-td-comment", L.DomUtil.create('tr', "osb-tr-comment", table));
td.setAttribute("colspan","2");
td.setAttribute("charoff","2");
td.textContent = rawbug[4][i];
}
function create_link(ul, text) {
var a = L.DomUtil.create('a', null,
L.DomUtil.create('li', null, ul));
a.href = '#';
a.textContent = L.i18n(text);
return a;
};
var ul = L.DomUtil.create('ul', null, newContent);
var _this = this;
var bug = this.bugs[id];
function showComment(title, add_comment) {
h1.textContent_old = h1.textContent;
h1.textContent = L.i18n(title);
var form = _this.createCommentForm();
form.osbid.value = id;
form.cancel.onclick = function (e) {
h1.textContent = h1.textContent_old;
newContent.removeChild(form);
newContent.appendChild(ul);
}
form.ok.onclick = function(e) {
bug.closePopup();
if (!add_comment)
_this.closeBug(form);
else
_this.submitComment(form);
return false;
};
newContent.appendChild(form);
newContent.removeChild(ul);
return false;
};
if (!isclosed && !this.options.readonly) {
var a;
a = create_link(ul, "Add comment");
a.onclick = function(e) { return showComment("Add comment", true); }
a = create_link(ul, "Mark as Fixed");
a.onclick = function(e) { return showComment("Close bug", false); }
}
var a = create_link(ul, "JOSM");
a.onclick = function() { _this.remoteEdit(rawbug[0]); };
var a = create_link(ul, "Link");
var vars = {lat:rawbug[0].lat, lon:rawbug[0].lng, zoom:this.options.permalinkZoom, bugid:id}
if (this.options.permalinkUrl)
a.href = L.Util.template(this.options.permalinkUrl, vars)
else
a.href = location.protocol + '//' + location.host + location.pathname +
L.Util.getParamString(vars)
bug._popup_content = newContent;
bug.bindPopup(newContent, this.options.popupOptions);
bug._popup.options.maxWidth=410;
bug._popup.options.minWidth=410;
bug.on('mouseover', bug.openTempPopup, bug);
},
submitComment: function(form) {
if (!form.osbcomment.value) return;
var nickname = form.osbnickname.value || this.options.username;
this.apiRequest("editPOIexec"
+ "?id="+encodeURIComponent(form.osbid.value)
+ "&text="+encodeURIComponent(form.osbcomment.value + " [" + nickname + "]")
+ "&format=js", true
);
this.set_cookie("osbUsername",nickname);
this.options.username=nickname;
},
closeBug: function(form) {
var id = form.osbid.value;
this.submitComment(form);
this.apiRequest("closePOIexec"
+ "?id="+encodeURIComponent(id)
+ "&format=js", true
);
},
createCommentForm: function(elt) {
var form = L.DomUtil.create("form", 'osb-add-comment', elt);
var content = '';
content += '<input name="osbid" type="hidden"/>';
content += '<input name="osblat" type="hidden"/>';
content += '<input name="osblon" type="hidden"/>';
content += '<div><span class="osb-inputlabel">'+L.i18n('Nickname')+':</span><input type="text" name="osbnickname"></div>';
content += '<div><span class="osb-inputlabel">'+L.i18n('Comment')+':</span><input type="text" name="osbcomment"></div>';
content += '<div class="osb-formfooter"><input type="submit" name="ok"/><input type="button" name="cancel"/></div>';
form.innerHTML = content;
form.ok.value = L.i18n('OK');
form.cancel.value = L.i18n('Cancel');
form.osbnickname.value = this.options.username;
return form;
},
addBug: function(e) {
var newContent = L.DomUtil.create('div', 'osb-popup');
newContent.innerHTML += '<h1>'+L.i18n("New bug")+'</h1>';
newContent.innerHTML += '<div class="osbCreateInfo">'+L.i18n("Find your bug?")+'<br />'+L.i18n("Contact details and someone will fix it.")+'</div>';
var popup = new L.Popup();
var _this = this;
var form = this.createCommentForm(newContent);
form.osblat.value = e.latlng.lat;
form.osblon.value = e.latlng.lng;
form.ok.value = L.i18n("Add comment");
form.onsubmit = function(e) {
_this._map.closePopup(popup);
_this.createBug(form);
return false;
};
form.cancel.onclick = function(e) { _this._map.closePopup(popup); }
popup.setLatLng(e.latlng);
popup.setContent(newContent);
popup.options.maxWidth=410;
popup.options.minWidth=410;
popup.options.className += ' osb osbCreate'
this._map.openPopup(popup);
},
createBug: function(form) {
if (!form.osbcomment.value) return;
var nickname = form.osbnickname.value || this.options.username;
this.apiRequest("addPOIexec"
+ "?lat="+encodeURIComponent(form.osblat.value)
+ "&lon="+encodeURIComponent(form.osblon.value)
+ "&text="+encodeURIComponent(form.osbcomment.value + " [" + nickname + "]")
+ "&format=js", true
);
this.set_cookie("osbUsername",nickname);
this.options.username=nickname;
},
remoteEdit: function(x) {
var ydelta = this.options.editArea || 0.01;
var xdelta = ydelta * 2;
var p = [ 'left=' + (x.lng - xdelta), 'bottom=' + (x.lat - ydelta)
, 'right=' + (x.lng + xdelta), 'top=' + (x.lat + ydelta)];
var url = 'http://localhost:8111/load_and_zoom?' + p.join('&');
var frame = L.DomUtil.create('iframe', null);
frame.style.display = 'none';
frame.src = url;
document.body.appendChild(frame);
frame.onload = function(e) { document.body.removeChild(frame); };
return false;
}
})
L.OpenStreetBugs.setCSS = function() {
if(L.OpenStreetBugs.setCSS.done)
return;
else
L.OpenStreetBugs.setCSS.done = true;
// See http://www.hunlock.com/blogs/Totally_Pwn_CSS_with_Javascript
var idx = 0;
var addRule = function(selector, rules) {
var s = document.styleSheets[0];
var rule;
if(s.addRule) // M$IE
rule = s.addRule(selector, rules, idx);
else
rule = s.insertRule(selector + " { " + rules + " }", idx);
s.style = L.Util.extend(s.style || {}, rules);
idx++;
};
addRule(".osb-popup dl", 'margin:0; padding:0;');
addRule(".osb-popup dt", 'margin:0; padding:0; font-weight:bold; float:left; clear:left;');
addRule(".osb-popup dt:after", 'content: ": ";');
addRule("* html .osb-popup dt", 'margin-right:1ex;');
addRule(".osb-popup dd", 'margin:0; padding:0;');
addRule(".osb-popup ul.buttons", 'list-style-type:none; padding:0; margin:0;');
addRule(".osb-popup ul.buttons li", 'display:inline; margin:0; padding:0;');
addRule(".osb-popup h3", 'font-size:1.2em; margin:.2em 0 .7em 0;');
};
function putAJAXMarker(id, lon, lat, text, closed)
{
var comments = text.split(/<hr \/>/);
var comments_only = []
var nickname = [];
var datetime = [];
var info = null;
var isplit = 0;
for(var i=0; i<comments.length; i++) {
info = null;
isplit = 0;
comments[i] = comments[i].replace(/&quot;/g, "\"").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
isplit = comments[i].lastIndexOf("[");
if (isplit > 0) {
comments_only[i] = comments[i].substr(0,isplit-1);
info = comments[i].substr(isplit+1);
nickname[i] = info.substr(0,info.lastIndexOf(","));
datetime[i] = info.substr(info.lastIndexOf(",")+2);
datetime[i] = datetime[i].substr(0,datetime[i].lastIndexOf("]"));
}
else {
comments_only[i] = comments[i];
}
}
var old = putAJAXMarker.bugs[id];
putAJAXMarker.bugs[id] = [
new L.LatLng(lat, lon),
comments,
closed,
text,
comments_only,
nickname,
datetime
];
var force = (old && old[3]) != text;
for(var i=0; i<putAJAXMarker.layers.length; i++)
putAJAXMarker.layers[i].createMarker(id, force);
}
function osbResponse(error)
{
if(error)
alert("Error: "+error);
return;
for(var i=0; i<putAJAXMarker.layers.length; i++)
putAJAXMarker.layers[i].loadBugs();
}
putAJAXMarker.layers = [ ];
putAJAXMarker.bugs = { };
L.Marker.include({
openTempPopup: function() {
this.openPopup();
this.off('click', this.openPopup, this);
function onclick() {
this.off('mouseout', onout, this);
this.off('click', onclick, this);
this.on('click', this.openPopup, this)
}
function onout() {
onclick.call(this);
this.closePopup();
};
this.on("mouseout", onout, this);
this.on("click", onclick, this);
}
});
L.i18n = function(s) { return (L.i18n.lang[L.i18n.current] || {})[s] || s; }
L.i18n.current = 'ru';
L.i18n.lang = {};
L.i18n.extend = function(lang, args) {
L.i18n.lang[lang] = L.Util.extend(L.i18n.lang[lang] || {}, args)
};
L.i18n.extend('ru', {
"Fixed Error":"Ошибка исправлена",
"Unresolved Error":"Неисправленная ошибка",
"Active Error":"Ошибка уточняется",
"Description":"Описание",
"Comment":"Описание",
"Add comment":"Дополнить",
"Mark as Fixed":"Исправлено",
"Link":"Ссылка",
"Cancel":"Отмена",
"New bug":"Я нашел ошибку",
"Find your bug?":"Нашли ошибку?",
"Contact details and someone will fix it.":"Напишите подробнее и кто-нибудь её исправит."
});

View file

@ -0,0 +1,124 @@
L.BingLayer = L.TileLayer.extend({
options: {
subdomains: [0, 1, 2, 3],
type: 'Aerial',
attribution: 'Bing',
culture: ''
},
initialize: function(key, options) {
L.Util.setOptions(this, options);
this._key = key;
this._url = null;
this.meta = {};
this.loadMetadata();
},
tile2quad: function(x, y, z) {
var quad = '';
for (var i = z; i > 0; i--) {
var digit = 0;
var mask = 1 << (i - 1);
if ((x & mask) != 0) digit += 1;
if ((y & mask) != 0) digit += 2;
quad = quad + digit;
}
return quad;
},
getTileUrl: function(p, z) {
var z = this._getZoomForUrl();
var subdomains = this.options.subdomains,
s = this.options.subdomains[Math.abs((p.x + p.y) % subdomains.length)];
return this._url.replace('{subdomain}', s)
.replace('{quadkey}', this.tile2quad(p.x, p.y, z))
.replace('http:', document.location.protocol)
.replace('{culture}', this.options.culture);
},
loadMetadata: function() {
var _this = this;
var cbid = '_bing_metadata_' + L.Util.stamp(this);
window[cbid] = function (meta) {
_this.meta = meta;
window[cbid] = undefined;
var e = document.getElementById(cbid);
e.parentNode.removeChild(e);
if (meta.errorDetails) {
if (window.console) console.log("Leaflet Bing Plugin Error - Got metadata: " + meta.errorDetails);
return;
}
_this.initMetadata();
};
var url = document.location.protocol + "//dev.virtualearth.net/REST/v1/Imagery/Metadata/" + this.options.type + "?include=ImageryProviders&jsonp=" + cbid + "&key=" + this._key;
var script = document.createElement("script");
script.type = "text/javascript";
script.src = url;
script.id = cbid;
document.getElementsByTagName("head")[0].appendChild(script);
},
initMetadata: function() {
var r = this.meta.resourceSets[0].resources[0];
this.options.subdomains = r.imageUrlSubdomains;
this._url = r.imageUrl;
this._providers = [];
if (r.imageryProviders) {
for (var i = 0; i < r.imageryProviders.length; i++) {
var p = r.imageryProviders[i];
for (var j = 0; j < p.coverageAreas.length; j++) {
var c = p.coverageAreas[j];
var coverage = {zoomMin: c.zoomMin, zoomMax: c.zoomMax, active: false};
var bounds = new L.LatLngBounds(
new L.LatLng(c.bbox[0]+0.01, c.bbox[1]+0.01),
new L.LatLng(c.bbox[2]-0.01, c.bbox[3]-0.01)
);
coverage.bounds = bounds;
coverage.attrib = p.attribution;
this._providers.push(coverage);
}
}
}
this._update();
},
_update: function() {
if (this._url == null || !this._map) return;
this._update_attribution();
L.TileLayer.prototype._update.apply(this, []);
},
_update_attribution: function() {
var bounds = this._map.getBounds();
var zoom = this._map.getZoom();
for (var i = 0; i < this._providers.length; i++) {
var p = this._providers[i];
if ((zoom <= p.zoomMax && zoom >= p.zoomMin) &&
bounds.intersects(p.bounds)) {
if (!p.active && this._map.attributionControl)
this._map.attributionControl.addAttribution(p.attrib);
p.active = true;
} else {
if (p.active && this._map.attributionControl)
this._map.attributionControl.removeAttribution(p.attrib);
p.active = false;
}
}
},
onRemove: function(map) {
for (var i = 0; i < this._providers.length; i++) {
var p = this._providers[i];
if (p.active && this._map.attributionControl) {
this._map.attributionControl.removeAttribution(p.attrib);
p.active = false;
}
}
L.TileLayer.prototype.onRemove.apply(this, [map]);
}
});
L.bingLayer = function (key, options) {
return new L.BingLayer(key, options);
};

View file

@ -0,0 +1,202 @@
/*
* Google layer using Google Maps API
*/
//(function (google, L) {
L.Google = L.Class.extend({
includes: L.Mixin.Events,
options: {
minZoom: 0,
maxZoom: 18,
tileSize: 256,
subdomains: 'abc',
errorTileUrl: '',
attribution: '',
opacity: 1,
continuousWorld: false,
noWrap: false,
mapOptions: {
backgroundColor: '#dddddd'
}
},
// Possible types: SATELLITE, ROADMAP, HYBRID, TERRAIN
initialize: function(type, options) {
L.Util.setOptions(this, options);
this._ready = google.maps.Map != undefined;
if (!this._ready) L.Google.asyncWait.push(this);
this._type = type || 'SATELLITE';
},
onAdd: function(map, insertAtTheBottom) {
this._map = map;
this._insertAtTheBottom = insertAtTheBottom;
// create a container div for tiles
this._initContainer();
this._initMapObject();
// set up events
map.on('viewreset', this._resetCallback, this);
this._limitedUpdate = L.Util.limitExecByInterval(this._update, 150, this);
map.on('move', this._update, this);
map.on('zoomanim', this._handleZoomAnim, this);
//20px instead of 1em to avoid a slight overlap with google's attribution
map._controlCorners['bottomright'].style.marginBottom = "20px";
this._reset();
this._update();
},
onRemove: function(map) {
this._map._container.removeChild(this._container);
//this._container = null;
this._map.off('viewreset', this._resetCallback, this);
this._map.off('move', this._update, this);
this._map.off('zoomanim', this._handleZoomAnim, this);
map._controlCorners['bottomright'].style.marginBottom = "0em";
//this._map.off('moveend', this._update, this);
},
getAttribution: function() {
return this.options.attribution;
},
setOpacity: function(opacity) {
this.options.opacity = opacity;
if (opacity < 1) {
L.DomUtil.setOpacity(this._container, opacity);
}
},
setElementSize: function(e, size) {
e.style.width = size.x + "px";
e.style.height = size.y + "px";
},
_initContainer: function() {
var tilePane = this._map._container,
first = tilePane.firstChild;
if (!this._container) {
this._container = L.DomUtil.create('div', 'leaflet-google-layer leaflet-top leaflet-left');
this._container.id = "_GMapContainer_" + L.Util.stamp(this);
this._container.style.zIndex = "auto";
}
if (true) {
tilePane.insertBefore(this._container, first);
this.setOpacity(this.options.opacity);
this.setElementSize(this._container, this._map.getSize());
}
},
_initMapObject: function() {
if (!this._ready) return;
this._google_center = new google.maps.LatLng(0, 0);
var map = new google.maps.Map(this._container, {
center: this._google_center,
zoom: 0,
tilt: 0,
mapTypeId: google.maps.MapTypeId[this._type],
disableDefaultUI: true,
keyboardShortcuts: false,
draggable: false,
disableDoubleClickZoom: true,
scrollwheel: false,
streetViewControl: false,
styles: this.options.mapOptions.styles,
backgroundColor: this.options.mapOptions.backgroundColor
});
var _this = this;
this._reposition = google.maps.event.addListenerOnce(map, "center_changed",
function() { _this.onReposition(); });
this._google = map;
google.maps.event.addListenerOnce(map, "idle",
function() { _this._checkZoomLevels(); });
},
_checkZoomLevels: function() {
//setting the zoom level on the Google map may result in a different zoom level than the one requested
//(it won't go beyond the level for which they have data).
// verify and make sure the zoom levels on both Leaflet and Google maps are consistent
if (this._google.getZoom() !== this._map.getZoom()) {
//zoom levels are out of sync. Set the leaflet zoom level to match the google one
this._map.setZoom( this._google.getZoom() );
}
},
_resetCallback: function(e) {
this._reset(e.hard);
},
_reset: function(clearOldContainer) {
this._initContainer();
},
_update: function(e) {
if (!this._google) return;
this._resize();
var center = e && e.latlng ? e.latlng : this._map.getCenter();
var _center = new google.maps.LatLng(center.lat, center.lng);
this._google.setCenter(_center);
this._google.setZoom(this._map.getZoom());
this._checkZoomLevels();
//this._google.fitBounds(google_bounds);
},
_resize: function() {
var size = this._map.getSize();
if (this._container.style.width == size.x &&
this._container.style.height == size.y)
return;
this.setElementSize(this._container, size);
this.onReposition();
},
_handleZoomAnim: function (e) {
var center = e.center;
var _center = new google.maps.LatLng(center.lat, center.lng);
this._google.setCenter(_center);
this._google.setZoom(e.zoom);
},
onReposition: function() {
if (!this._google) return;
google.maps.event.trigger(this._google, "resize");
}
});
L.Google.asyncWait = [];
L.Google.asyncInitialize = function() {
var i;
for (i = 0; i < L.Google.asyncWait.length; i++) {
var o = L.Google.asyncWait[i];
o._ready = true;
if (o._container) {
o._initMapObject();
o._update();
}
}
L.Google.asyncWait = [];
};
//})(window.google, L)

View file

@ -0,0 +1,161 @@
/*
* L.TileLayer is used for standard xyz-numbered tile layers.
*/
//(function (ymaps, L) {
L.Yandex = L.Class.extend({
includes: L.Mixin.Events,
options: {
minZoom: 0,
maxZoom: 18,
attribution: '',
opacity: 1,
traffic: false
},
// Possible types: map, satellite, hybrid, publicMap, publicMapHybrid
initialize: function(type, options) {
L.Util.setOptions(this, options);
this._type = "yandex#" + (type || 'map');
},
onAdd: function(map, insertAtTheBottom) {
this._map = map;
this._insertAtTheBottom = insertAtTheBottom;
// create a container div for tiles
this._initContainer();
this._initMapObject();
// set up events
map.on('viewreset', this._resetCallback, this);
this._limitedUpdate = L.Util.limitExecByInterval(this._update, 150, this);
map.on('move', this._update, this);
map._controlCorners['bottomright'].style.marginBottom = "3em";
this._reset();
this._update(true);
},
onRemove: function(map) {
this._map._container.removeChild(this._container);
this._map.off('viewreset', this._resetCallback, this);
this._map.off('move', this._update, this);
map._controlCorners['bottomright'].style.marginBottom = "0em";
},
getAttribution: function() {
return this.options.attribution;
},
setOpacity: function(opacity) {
this.options.opacity = opacity;
if (opacity < 1) {
L.DomUtil.setOpacity(this._container, opacity);
}
},
setElementSize: function(e, size) {
e.style.width = size.x + "px";
e.style.height = size.y + "px";
},
_initContainer: function() {
var tilePane = this._map._container,
first = tilePane.firstChild;
if (!this._container) {
this._container = L.DomUtil.create('div', 'leaflet-yandex-layer leaflet-top leaflet-left');
this._container.id = "_YMapContainer_" + L.Util.stamp(this);
this._container.style.zIndex = "auto";
}
if (this.options.overlay) {
first = this._map._container.getElementsByClassName('leaflet-map-pane')[0];
first = first.nextSibling;
// XXX: Bug with layer order
if (L.Browser.opera)
this._container.className += " leaflet-objects-pane";
}
tilePane.insertBefore(this._container, first);
this.setOpacity(this.options.opacity);
this.setElementSize(this._container, this._map.getSize());
},
_initMapObject: function() {
if (this._yandex) return;
// Check that ymaps.Map is ready
if (ymaps.Map === undefined) {
if (console) {
console.debug("L.Yandex: Waiting on ymaps.load('package.map')");
}
return ymaps.load(["package.map"], this._initMapObject, this);
}
// If traffic layer is requested check if control.TrafficControl is ready
if (this.options.traffic)
if (ymaps.control === undefined ||
ymaps.control.TrafficControl === undefined) {
if (console) {
console.debug("L.Yandex: loading traffic and controls");
}
return ymaps.load(["package.traffic", "package.controls"],
this._initMapObject, this);
}
var map = new ymaps.Map(this._container, {center: [0,0], zoom: 0, behaviors: []});
if (this.options.traffic)
map.controls.add(new ymaps.control.TrafficControl({shown: true}));
if (this._type == "yandex#null") {
this._type = new ymaps.MapType("null", []);
map.container.getElement().style.background = "transparent";
}
map.setType(this._type);
this._yandex = map;
this._update(true);
},
_resetCallback: function(e) {
this._reset(e.hard);
},
_reset: function(clearOldContainer) {
this._initContainer();
},
_update: function(force) {
if (!this._yandex) return;
this._resize(force);
var center = this._map.getCenter();
var _center = [center.lat, center.lng];
var zoom = this._map.getZoom();
if (force || this._yandex.getZoom() != zoom)
this._yandex.setZoom(zoom);
this._yandex.panTo(_center, {duration: 0, delay: 0});
},
_resize: function(force) {
var size = this._map.getSize(), style = this._container.style;
if (style.width == size.x + "px" &&
style.height == size.y + "px")
if (force != true) return;
this.setElementSize(this._container, size);
var b = this._map.getBounds(), sw = b.getSouthWest(), ne = b.getNorthEast();
this._yandex.container.fitToViewport();
}
});
//})(ymaps, L)

View file

@ -0,0 +1,80 @@
//#include "GPX.js"
(function() {
function d2h(d) {
var hex = '0123456789ABCDEF';
var r = '';
d = Math.floor(d);
while (d != 0) {
r = hex[d % 16] + r;
d = Math.floor(d / 16);
}
while (r.length < 2) r = '0' + r;
return r;
}
function gradient(color) {
// First arc (0, PI) in HSV colorspace
function f2h(d) { return d2h(256 * d); }
if (color < 0)
return "#FF0000";
else if (color < 1.0/3)
return "#FF" + f2h(3 * color) + "00";
else if (color < 2.0/3)
return "#" + f2h(2 - 3 * color) + "FF00";
else if (color < 1)
return "#00FF" + f2h(3 * color - 2);
else
return "#00FFFF";
};
function gpx2time(s) {
// 2011-09-24T12:07:53Z
if (s.length != 10 + 1 + 8 + 1)
return new Date();
return new Date(s);
};
L.GPX.include({
options: {
maxSpeed: 110,
chunks: 200
},
speedSplitEnable: function(options) {
L.Util.setOptions(this, options);
return this.on('addline', this.speed_split, this);
},
speedSplitDisable: function() {
return this.off('addline', this.speed_split, this);
},
speed_split: function(e) {
var l = e.line.pop(), ll = l.getLatLngs();
var chunk = Math.floor(ll.length / this.options.chunks);
if (chunk < 3) chunk = 3;
var p = null;
for (var i = 0; i < ll.length; i += chunk) {
var d = 0, t = null;
if (i + chunk > ll.length)
chunk = ll.length - i;
for (var j = 0; j < chunk; j++) {
if (p) d += p.distanceTo(ll[i+j]);
p = ll[i + j];
if (!t) t = gpx2time(p.meta.time);
}
p = ll[i + chunk - 1];
t = (gpx2time(p.meta.time) - t) / (3600 * 1000);
var speed = 0.001 * d / t;
//console.info('Dist: ' + d + "; Speed: " + speed);
var color = gradient(speed / this.options.maxSpeed);
var l = new L.Polyline(ll.slice(i, i+chunk+1), {color: color, weight: 2, opacity: 1});
l.bindPopup('Dist: ' + d.toFixed() + "m; Speed: " + speed.toFixed(2) + " km/h");
e.line.push(l);
}
}
});
})();

View file

@ -0,0 +1,143 @@
/*global L: true */
L.GPX = L.FeatureGroup.extend({
initialize: function(gpx, options) {
L.Util.setOptions(this, options);
this._gpx = gpx;
this._layers = {};
if (gpx) {
this.addGPX(gpx, options, this.options.async);
}
},
loadXML: function(url, cb, options, async) {
if (async == undefined) async = this.options.async;
if (options == undefined) options = this.options;
var req = new window.XMLHttpRequest();
req.open('GET', url, async);
try {
req.overrideMimeType('text/xml'); // unsupported by IE
} catch(e) {}
req.onreadystatechange = function() {
if (req.readyState != 4) return;
if(req.status == 200) cb(req.responseXML, options);
};
req.send(null);
},
_humanLen: function(l) {
if (l < 2000)
return l.toFixed(0) + " m";
else
return (l/1000).toFixed(1) + " km";
},
_polylineLen: function(line)//line is a L.Polyline()
{
var ll = line._latlngs;
var d = 0, p = null;
for (var i = 0; i < ll.length; i++)
{
if(i && p)
d += p.distanceTo(ll[i]);
p = ll[i];
}
return d;
},
addGPX: function(url, options, async) {
var _this = this;
var cb = function(gpx, options) { _this._addGPX(gpx, options) };
this.loadXML(url, cb, options, async);
},
_addGPX: function(gpx, options) {
var layers = this.parseGPX(gpx, options);
if (!layers) return;
this.addLayer(layers);
this.fire("loaded");
},
parseGPX: function(xml, options) {
var j, i, el, layers = [];
var named = false, tags = [['rte','rtept'], ['trkseg','trkpt']];
for (j = 0; j < tags.length; j++) {
el = xml.getElementsByTagName(tags[j][0]);
for (i = 0; i < el.length; i++) {
var l = this.parse_trkseg(el[i], xml, options, tags[j][1]);
for (var k = 0; k < l.length; k++) {
if (this.parse_name(el[i], l[k])) named = true;
layers.push(l[k]);
}
}
}
el = xml.getElementsByTagName('wpt');
if (options.display_wpt != false) {
for (i = 0; i < el.length; i++) {
var l = this.parse_wpt(el[i], xml, options);
if (!l) continue;
if (this.parse_name(el[i], l)) named = true;
layers.push(l);
}
}
if (!layers.length) return;
var layer = layers[0];
if (layers.length > 1)
layer = new L.FeatureGroup(layers);
if (!named) this.parse_name(xml, layer);
return layer;
},
parse_name: function(xml, layer) {
var i, el, txt="", name, descr="", len=0;
el = xml.getElementsByTagName('name');
if (el.length)
name = el[0].childNodes[0].nodeValue;
el = xml.getElementsByTagName('desc');
for (i = 0; i < el.length; i++) {
for (var j = 0; j < el[i].childNodes.length; j++)
descr = descr + el[i].childNodes[j].nodeValue;
}
if(layer instanceof L.Path)
len = this._polylineLen(layer);
if (name) txt += "<h2>" + name + "</h2>" + descr;
if (len) txt += "<p>" + this._humanLen(len) + "</p>";
if (layer && layer._popup === undefined) layer.bindPopup(txt);
return txt;
},
parse_trkseg: function(line, xml, options, tag) {
var el = line.getElementsByTagName(tag);
if (!el.length) return [];
var coords = [];
for (var i = 0; i < el.length; i++) {
var ll = new L.LatLng(el[i].getAttribute('lat'),
el[i].getAttribute('lon'));
ll.meta = {};
for (var j in el[i].childNodes) {
var e = el[i].childNodes[j];
if (!e.tagName) continue;
ll.meta[e.tagName] = e.textContent;
}
coords.push(ll);
}
var l = [new L.Polyline(coords, options)];
this.fire('addline', {line:l})
return l;
},
parse_wpt: function(e, xml, options) {
var m = new L.Marker(new L.LatLng(e.getAttribute('lat'),
e.getAttribute('lon')), options);
this.fire('addpoint', {point:m});
return m;
}
});

View file

@ -0,0 +1,359 @@
/*global L: true */
L.KML = L.FeatureGroup.extend({
options: {
async: true
},
initialize: function(kml, options) {
L.Util.setOptions(this, options);
this._kml = kml;
this._layers = {};
if (kml) {
this.addKML(kml, options, this.options.async);
}
},
loadXML: function(url, cb, options, async) {
if (async == undefined) async = this.options.async;
if (options == undefined) options = this.options;
var req = new window.XMLHttpRequest();
req.open('GET', url, async);
try {
req.overrideMimeType('text/xml'); // unsupported by IE
} catch(e) {}
req.onreadystatechange = function() {
if (req.readyState != 4) return;
if(req.status == 200) cb(req.responseXML, options);
};
req.send(null);
},
addKML: function(url, options, async) {
var _this = this;
var cb = function(gpx, options) { _this._addKML(gpx, options) };
this.loadXML(url, cb, options, async);
},
_addKML: function(xml, options) {
var layers = L.KML.parseKML(xml);
if (!layers || !layers.length) return;
for (var i = 0; i < layers.length; i++)
{
this.fire('addlayer', {
layer: layers[i]
});
this.addLayer(layers[i]);
}
this.latLngs = L.KML.getLatLngs(xml);
this.fire("loaded");
},
latLngs: []
});
L.Util.extend(L.KML, {
parseKML: function (xml) {
var style = this.parseStyle(xml);
this.parseStyleMap(xml, style);
var el = xml.getElementsByTagName("Folder");
var layers = [], l;
for (var i = 0; i < el.length; i++) {
if (!this._check_folder(el[i])) { continue; }
l = this.parseFolder(el[i], style);
if (l) { layers.push(l); }
}
el = xml.getElementsByTagName('Placemark');
for (var j = 0; j < el.length; j++) {
if (!this._check_folder(el[j])) { continue; }
l = this.parsePlacemark(el[j], xml, style);
if (l) { layers.push(l); }
}
return layers;
},
// Return false if e's first parent Folder is not [folder]
// - returns true if no parent Folders
_check_folder: function (e, folder) {
e = e.parentElement;
while (e && e.tagName !== "Folder")
{
e = e.parentElement;
}
return !e || e === folder;
},
parseStyle: function (xml) {
var style = {};
var sl = xml.getElementsByTagName("Style");
//for (var i = 0; i < sl.length; i++) {
var attributes = {color: true, width: true, Icon: true, href: true,
hotSpot: true};
function _parse(xml) {
var options = {};
for (var i = 0; i < xml.childNodes.length; i++) {
var e = xml.childNodes[i];
var key = e.tagName;
if (!attributes[key]) { continue; }
if (key === 'hotSpot')
{
for (var j = 0; j < e.attributes.length; j++) {
options[e.attributes[j].name] = e.attributes[j].nodeValue;
}
} else {
var value = e.childNodes[0].nodeValue;
if (key === 'color') {
options.opacity = parseInt(value.substring(0, 2), 16) / 255.0;
options.color = "#" + value.substring(6, 8) + value.substring(4, 6) + value.substring(2, 4);
} else if (key === 'width') {
options.weight = value;
} else if (key === 'Icon') {
ioptions = _parse(e);
if (ioptions.href) { options.href = ioptions.href; }
} else if (key === 'href') {
options.href = value;
}
}
}
return options;
}
for (var i = 0; i < sl.length; i++) {
var e = sl[i], el;
var options = {}, poptions = {}, ioptions = {};
el = e.getElementsByTagName("LineStyle");
if (el && el[0]) { options = _parse(el[0]); }
el = e.getElementsByTagName("PolyStyle");
if (el && el[0]) { poptions = _parse(el[0]); }
if (poptions.color) { options.fillColor = poptions.color; }
if (poptions.opacity) { options.fillOpacity = poptions.opacity; }
el = e.getElementsByTagName("IconStyle");
if (el && el[0]) { ioptions = _parse(el[0]); }
if (ioptions.href) {
// save anchor info until the image is loaded
options.icon = new L.KMLIcon({
iconUrl: ioptions.href,
shadowUrl: null,
iconAnchorRef: {x: ioptions.x, y: ioptions.y},
iconAnchorType: {x: ioptions.xunits, y: ioptions.yunits}
});
}
style['#' + e.getAttribute('id')] = options;
}
return style;
},
parseStyleMap: function (xml, existingStyles) {
var sl = xml.getElementsByTagName("StyleMap");
for (var i = 0; i < sl.length; i++) {
var e = sl[i], el;
var smKey, smStyleUrl;
el = e.getElementsByTagName("key");
if (el && el[0]) { smKey = el[0].textContent; }
el = e.getElementsByTagName("styleUrl");
if (el && el[0]) { smStyleUrl = el[0].textContent; }
if (smKey=='normal')
{
existingStyles['#' + e.getAttribute('id')] = existingStyles[smStyleUrl];
}
}
return;
},
parseFolder: function (xml, style) {
var el, layers = [], l;
el = xml.getElementsByTagName('Folder');
for (var i = 0; i < el.length; i++) {
if (!this._check_folder(el[i], xml)) { continue; }
l = this.parseFolder(el[i], style);
if (l) { layers.push(l); }
}
el = xml.getElementsByTagName('Placemark');
for (var j = 0; j < el.length; j++) {
if (!this._check_folder(el[j], xml)) { continue; }
l = this.parsePlacemark(el[j], xml, style);
if (l) { layers.push(l); }
}
if (!layers.length) { return; }
if (layers.length === 1) { return layers[0]; }
return new L.FeatureGroup(layers);
},
parsePlacemark: function (place, xml, style) {
var i, j, el, options = {};
el = place.getElementsByTagName('styleUrl');
for (i = 0; i < el.length; i++) {
var url = el[i].childNodes[0].nodeValue;
for (var a in style[url])
{
// for jshint
if (true)
{
options[a] = style[url][a];
}
}
}
var layers = [];
var parse = ['LineString', 'Polygon', 'Point'];
for (j in parse) {
// for jshint
if (true)
{
var tag = parse[j];
el = place.getElementsByTagName(tag);
for (i = 0; i < el.length; i++) {
var l = this["parse" + tag](el[i], xml, options);
if (l) { layers.push(l); }
}
}
}
if (!layers.length) {
return;
}
var layer = layers[0];
if (layers.length > 1) {
layer = new L.FeatureGroup(layers);
}
var name, descr = "";
el = place.getElementsByTagName('name');
if (el.length && el[0].childNodes.length) {
name = el[0].childNodes[0].nodeValue;
}
el = place.getElementsByTagName('description');
for (i = 0; i < el.length; i++) {
for (j = 0; j < el[i].childNodes.length; j++) {
descr = descr + el[i].childNodes[j].nodeValue;
}
}
if (name) {
layer.bindPopup("<h2>" + name + "</h2>" + descr);
}
return layer;
},
parseCoords: function (xml) {
var el = xml.getElementsByTagName('coordinates');
return this._read_coords(el[0]);
},
parseLineString: function (line, xml, options) {
var coords = this.parseCoords(line);
if (!coords.length) { return; }
return new L.Polyline(coords, options);
},
parsePoint: function (line, xml, options) {
var el = line.getElementsByTagName('coordinates');
if (!el.length) {
return;
}
var ll = el[0].childNodes[0].nodeValue.split(',');
return new L.KMLMarker(new L.LatLng(ll[1], ll[0]), options);
},
parsePolygon: function (line, xml, options) {
var el, polys = [], inner = [], i, coords;
el = line.getElementsByTagName('outerBoundaryIs');
for (i = 0; i < el.length; i++) {
coords = this.parseCoords(el[i]);
if (coords) {
polys.push(coords);
}
}
el = line.getElementsByTagName('innerBoundaryIs');
for (i = 0; i < el.length; i++) {
coords = this.parseCoords(el[i]);
if (coords) {
inner.push(coords);
}
}
if (!polys.length) {
return;
}
if (options.fillColor) {
options.fill = true;
}
if (polys.length === 1) {
return new L.Polygon(polys.concat(inner), options);
}
return new L.MultiPolygon(polys, options);
},
getLatLngs: function (xml) {
var el = xml.getElementsByTagName('coordinates');
var coords = [];
for (var j = 0; j < el.length; j++) {
// text might span many childNodes
coords = coords.concat(this._read_coords(el[j]));
}
return coords;
},
_read_coords: function (el) {
var text = "", coords = [], i;
for (i = 0; i < el.childNodes.length; i++) {
text = text + el.childNodes[i].nodeValue;
}
text = text.split(/[\s\n]+/);
for (i = 0; i < text.length; i++) {
var ll = text[i].split(',');
if (ll.length < 2) {
continue;
}
coords.push(new L.LatLng(ll[1], ll[0]));
}
return coords;
}
});
L.KMLIcon = L.Icon.extend({
createIcon: function () {
var img = this._createIcon('icon');
img.onload = function () {
var i = new Image();
i.src = this.src;
this.style.width = i.width + 'px';
this.style.height = i.height + 'px';
if (this.anchorType.x === 'UNITS_FRACTION' || this.anchorType.x === 'fraction') {
img.style.marginLeft = (-this.anchor.x * i.width) + 'px';
}
if (this.anchorType.y === 'UNITS_FRACTION' || this.anchorType.x === 'fraction') {
img.style.marginTop = (-(1 - this.anchor.y) * i.height) + 'px';
}
this.style.display = "";
};
return img;
},
_setIconStyles: function (img, name) {
L.Icon.prototype._setIconStyles.apply(this, [img, name])
// save anchor information to the image
img.anchor = this.options.iconAnchorRef;
img.anchorType = this.options.iconAnchorType;
}
});
L.KMLMarker = L.Marker.extend({
options: {
icon: new L.KMLIcon.Default()
}
});

View file

@ -0,0 +1,172 @@
/*global L: true */
L.OSM = L.FeatureGroup.extend({
options: {
async: true,
forceAll: false
},
initialize: function(url, options) {
L.Util.setOptions(this, options);
this._url = url;
this._layers = {};
if (url) {
this.addXML(url, options, this.options.async);
}
},
loadXML: function(url, cb, options, async) {
if (async == undefined) async = this.options.async;
if (options == undefined) options = this.options;
var req = new window.XMLHttpRequest();
req.open('GET', url, async);
req.overrideMimeType('text/xml');
req.onreadystatechange = function() {
if (req.readyState != 4) return;
if(req.status == 200) cb(req.responseXML, options);
};
req.send(null);
},
addXML: function(url, options, async) {
var _this = this;
var cb = function(xml, options) { _this._addXML(xml, options) };
this.loadXML(url, cb, options, async);
},
_addXML: function(xml, options) {
var layers = this.parseOSM(xml, options);
if (!layers) return;
this.addLayer(layers);
this.fire("loaded");
},
parseOSM: function(xml, options) {
var i, el, ll, layers = [];
var nodes = {};
var ways = {};
var named = false;
el = xml.getElementsByTagName('node');
for (i = 0; i < el.length; i++) {
var l = this.parse_node(el[i], xml, options);
if (l == undefined) continue;
nodes[l.osmid] = l;
if (!this.options.forceAll && !l.tags.length) continue;
var m = this.named_node(l, options);
if (!ll) ll = m.getLatLng();
if (this.parse_name(m, l, "Node")) named = true;
layers.push(m);
}
el = xml.getElementsByTagName('way');
for (i = 0; i < el.length; i++) {
if (i > 10) break;
var l = this.parse_way(el[i], nodes, options);
if (!l) continue;
if (!ll) ll = l.getLatLngs()[0];
if (this.parse_name(l, l, "Way")) named = true;
layers.push(l);
ways[l.osmid] = l;
}
el = xml.getElementsByTagName('relation');
for (i = 0; i < el.length; i++) {
if (i > 10) break;
var l = this.parse_relation(el[i], ways, options);
if (!l) continue;
if (!ll) ll = l.getLatLngs()[0];
if (this.parse_name(l, l, "Relation")) named = true;
layers.push(l);
}
if (!layers.length) return;
var layer = layers[0];
if (layers.length > 1)
layer = new L.FeatureGroup(layers);
if (!named) this.parse_name(xml, layer);
layer.focusPoint = ll;
return layer;
},
parse_name: function(layer, obj, obj_name) {
console.info("parse name");
console.info(this.options);
if (!this.options.forceAll)
if (!obj.tags || !obj.tags.length) return;
var i, txt = "<table>";
for (i = 0; i < obj.tags.length; i++) {
var t = obj.tags[i];
txt += "<tr><td>" + t.k + "</td><td>=</td><td>" + t.v + "</td></tr>";
}
txt += "</table>"
txt = "<h2>" + obj_name + " " + obj.osmid + "</h2>" + txt;
if (layer) layer.bindPopup(txt);
return txt;
},
parse_tags: function(line) {
var tags = [], el = line.getElementsByTagName('tag');
for (var i = 0; i < el.length; i++)
tags.push({k: el[i].getAttribute('k'), v: el[i].getAttribute('v')})
return tags;
},
parse_node: function(e) {
var n = { osmid: e.getAttribute('id')
, lat:e.getAttribute('lat')
, lon:e.getAttribute('lon')
}
n.ll = new L.LatLng(n.lat, n.lon);
n.tags = this.parse_tags(e);
return n;
},
parse_way: function(line, nodes, options) {
var el = line.getElementsByTagName('nd');
if (!el.length) return;
var coords = [], tags = [];
for (var i = 0; i < el.length; i++) {
var ref = el[i].getAttribute('ref'), n = nodes[ref];
if (!n) return;
coords.push(n.ll);
}
var layer = new L.Polyline(coords, options);
layer.tags = this.parse_tags(line);
layer.osmid = line.getAttribute('id')
return layer;
},
parse_relation: function(line, ways, options) {
var el = line.getElementsByTagName('member');
if (!el.length) return;
var rt, coords = [], tags = this.parse_tags(line);
for (var i = 0; i < tags.length; i++)
if (tags[i].k == "type") rt = tags[i].v;
if (rt != "multipolygon" && rt != "boundary" && rt != "waterway")
return;
for (var i = 0; i < el.length; i++) {
var mt = el[i].getAttribute("type"), ref = el[i].getAttribute("ref");
if (mt != "way") continue;
var w = ways[ref];
console.info("Way: " + ref + " " + w);
if (!w) return;
coords.push(w);
}
console.info("Coords: " + coords.length);
if (!coords.length) return;
var layer = new L.MultiPolyline(coords, options);
layer.tags = this.parse_tags(line);
layer.osmid = line.getAttribute('id')
return layer;
},
named_node: function(node, options) {
var marker = new L.Marker(new L.LatLng(node.lat, node.lon), options);
return marker;
}
});

View file

@ -0,0 +1,22 @@
{
"author": "Pavel Shramov",
"name": "leaflet-plugins",
"version": "1.0.1",
"description": "Miscellaneous plugins for Leaflet library for services that need to display route information and need satellite imagery from different providers",
"repository": {
"type": "git",
"url": "git@github.com:shramov/leaflet-plugins.git"
},
"contributors": [
"Bruno Bergot <bruno@eliaz.fr>",
"Andrey Lushchick <andrew@lushchick.org>"
],
"keywords": [
"leaflet",
"plugins",
"map",
"google",
"bing",
"yandex"
]
}

View file

@ -0,0 +1,13 @@
{
"name": "leaflet-routing",
"homepage": "https://github.com/Turistforeningen/leaflet-routing",
"_release": "9d66697f2a",
"_resolution": {
"type": "branch",
"branch": "gh-pages",
"commit": "9d66697f2a0e547b61a48c7a5358f5f0bba280fe"
},
"_source": "git://github.com/Turistforeningen/leaflet-routing.git",
"_target": "gh-pages",
"_originalSource": "Turistforeningen/leaflet-routing"
}

View file

@ -0,0 +1 @@
*.swp

View file

@ -0,0 +1,22 @@
Copyright (c) 2013, Turistforeningen, Hans Kristian Flaatten
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are
permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of
conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list
of conditions and the following disclaimer in the documentation and/or other materials
provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -0,0 +1,80 @@
Routing in Leaflet
==================
Research and development of a Leaflet.js routing module. The module will be capable of the routing markers and returning geojson in various ways.
![Prototype Routing using Leaflet](https://raw.github.com/Turistforeningen/leaflet-routing/gh-pages/images/promo.gif)
## Usage
```javascript
var routing = new L.Routing({
position: 'topright'
,routing: {
router: myRouter
}
,snapping: {
layers: [snapping]
,sensitivity: 15
,vertexonly: false
}
});
map.addControl(routing);
```
### Enable Drawing
```javascript
routing.draw(true);
```
### Enable Routing `NOT IMPLEMENTED`
```javascript
routing.routing(true);
```
### Enable Snapping `NOT IMPLEMETED`
```javascript
routing.snapping(true);
```
### Get first waypoint
```javascript
var first = routing.getFirst();
```
### Get last waypoint
```javascript
var last = routing.getLast();
```
### Get all waypoints
```javascript
var waypointsArray = routing.getWaypoints();
```
### Routing to Polyline
```javascript
var polyline = routing.toPolyline();
```
### To GeoJSON
```javascript
var geoJSON3D = routing.toGeoJSON();
var geoJSON2D = routing.toGeoJSON(false);
```
## Copyright
Copyright (c) 2013, Turistforeningen
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -0,0 +1,68 @@
#map {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
div.line-mouse-marker {
background-color: #ffffff;
border: 2px solid black;
border-radius: 10px;
}
#export {
position: absolute;
right: 10px;
top: 10px;
background-color: rgb(228,225,218);
border: 1px solid rgb(196, 196, 196);
padding: 10px;
z-index: 3000;
}
#search {
position: relative;
margin: 10px auto;
background-color: rgb(228,225,218);
border: 1px solid rgb(196, 196, 196);
padding: 10px;
z-index: 3000;
width: 300px;
}
#search input {
width: 240px;
}
.typeahead {
background-color: #fff;
}
.tt-dropdown-menu {
width: 240px;
padding: 8px 0;
background-color: #fff;
border: 1px solid #ccc;
}
.tt-suggestion {
padding: 3px 20px;
font-size: 18px;
line-height: 24px;
}
.tt-suggestion.tt-is-under-cursor {
color: #fff;
background-color: #0097cf;
}
.tt-suggestion p {
margin: 0;
}
#export input {
width: 70px;
}

228
bower_components/leaflet-routing/app.js vendored Normal file
View file

@ -0,0 +1,228 @@
/*
Routing capability using the Leaflet framework
Copyright (c) 2013, Turistforeningen, Hans Kristian Flaatten
https://github.com/Turistforeningen/leaflet-routing
*/
var routing, data;
(function() {
"use strict";
jQuery(function($) {
var api, apiKey, rUrl, sUrl, topo, map, snapping, inport, myRouter;
api = window.location.hash.substr(1).split('@');
if (api.length === 2) {
rUrl = 'http://' + api[1] + '/route/?coords='
sUrl = 'http://' + api[1] + '/bbox/?bbox=';
apiKey = api[0];
} else {
throw new Error('API auth failed');
}
topo = L.tileLayer('http://opencache.statkart.no/gatekeeper/gk/gk.open_gmaps?layers=topo2&zoom={z}&x={x}&y={y}', {
maxZoom: 16,
attribution: '<a href="http://www.statkart.no/">Statens kartverk</a>'
});
var summer = L.tileLayer('http://mt3.turistforeningen.no/prod/trail_summer/{z}/{x}/{y}.png', {
maxZoom: 16,
attribution: '<a href="http://www.turistforeningen.no/">DNT</a>'
});
var winter = L.tileLayer('http://mt3.turistforeningen.no/prod/trail_winter/{z}/{x}/{y}.png', {
maxZoom: 16,
attribution: '<a href="http://www.turistforeningen.no/">DNT</a>'
});
var cabin = L.tileLayer('http://mt3.turistforeningen.no/prod/cabin/{z}/{x}/{y}.png', {
maxZoom: 16,
attribution: '<a href="http://www.turistforeningen.no/">DNT</a>'
});
map = new L.Map('map', {
layers: [topo]
,center: new L.LatLng(61.5, 9)
,zoom: 13
});
cabin.addTo(map);
summer.addTo(map);
L.control.layers({'Topo 2': topo}, {
'DNTs merkede stier': summer
,'DNTs merkede vinterruter': winter
,'DNTs turisthytter': cabin
}, {
position: 'topleft'
}).addTo(map);
// Import Layer
inport = new L.layerGroup(null, {
style: {
opacity:0.5
,clickable:false
}
}).addTo(map);
// Snapping Layer
snapping = new L.geoJson(null, {
style: {
opacity:0
,clickable:false
}
}).addTo(map);
map.on('moveend', function() {
if (map.getZoom() > 12) {
var url;
url = sUrl + map.getBounds().toBBoxString() + '&callback=?';
$.getJSON(url).always(function(data, status) {
if (status === 'success') {
data = JSON.parse(data);
if (data.geometries && data.geometries.length > 0) {
snapping.clearLayers();
snapping.addData(data);
}
} else {
console.error('Could not load snapping data');
}
});
} else {
snapping.clearLayers();
}
});
map.fire('moveend');
// Routing Function
// @todo speed up geometryToLayer()
myRouter = function(l1, l2, cb) {
var req = $.getJSON(rUrl + [l1.lng, l1.lat, l2.lng, l2.lat].join(',') + '&callback=?');
req.always(function(data, status) {
if (status === 'success') {
try {
L.GeoJSON.geometryToLayer(JSON.parse(data)).eachLayer(function (layer) {
// 14026
var d1 = l1.distanceTo(layer._latlngs[0]);
var d2 = l2.distanceTo(layer._latlngs[layer._latlngs.length-1]);
if (d1 < 10 && d2 < 10) {
return cb(null, layer);
} else {
return cb(new Error('This has been discarded'));
}
});
} catch(e) {
return cb(new Error('Invalid JSON'));
}
} else {
return cb(new Error('Routing failed'));
}
});
}
// Leaflet Routing Module
routing = new L.Routing({
position: 'topleft'
,routing: {
router: myRouter
}
,snapping: {
layers: [snapping]
,sensitivity: 15
,vertexonly: false
}
});
map.addControl(routing);
routing.draw(true); // enable drawing mode
$('#eta-export').hide();
$('#eta-export').on('click', function() {
var id = $('#eta-id').val();
if (!id) { alert('Ingen tp_id definert!'); return; }
if (confirm('Eksport til ETA vil overskrive eksisterende geometri!')) {
var coords = routing.toGeoJSON().coordinates;
var data = [];
for (var i = 0; i < coords.length; i++) {
data.push(coords[i][1] + ' ' + coords[i][0]);
}
data = 'LINESTRING(' + data.join(',') + ')';
$.post('http://mintur.ut.no/lib/ajax/post_geom.php?api_key=' + apiKey + '&tp_id=' + id, {coords: data}, function(data) {
if (data.error) {
alert('Eksport feilet med feilkode ' + data.error);
} else if (data.success) {
window.location.href = 'http://mintur.ut.no/index.php?tp_id=' + id + '&tab=kart';
//alert('Eksport suksess!');
}
});
}
});
$('#eta-import').on('click', function() {
var id = $('#eta-id').val();
if (!id) { alert('Ingen tp_id definert!'); return; }
$.get('http://mintur.ut.no/lib/ajax/post_geom.php?api_key=' + apiKey + '&tp_id=' + id, function(data) {
if (data.error) {
alert('Import feilet med feilkode ' + data.error);
} else if (typeof data.coords !== 'undefined') {
$('#eta-import').hide();
$('#eta-export').show();
$('#eta-id').attr('readonly', 'readonly');
if (data.coords) {
data.coords = data.coords.replace('LINESTRING(', '').replace(')', '').split(',');
for (var i = 0; i < data.coords.length; i++) {
data.coords[i] = new L.LatLng(data.coords[i].split(' ')[1], data.coords[i].split(' ')[0]);
}
inport.clearLayers();
var p = new L.Polyline(data.coords, {clickable:false, color: '#000000', opacity: 0.4});
inport.addLayer(p);
map.fitBounds(p.getBounds());
}
}
});
});
function fetchSsrAc(search, cb) {
var result = [];
$.ajax({
url: "https://ws.geonorge.no/SKWS3Index/ssr/sok?navn=" + search + "*&epsgKode=4326&antPerSide=10"
,type: "GET"
,dataType: 'xml'
,success: function(xml) {
$(xml).find('sokRes > stedsnavn').each(function(){
result.push({
title: $(this).find('stedsnavn').text()
,lat: $(this).find('aust').text()
,lng: $(this).find('nord').text()
});
});
cb(null, result);
}
});
}
$('#ssr-search').typeahead({
remote: {
url: 'https://ws.geonorge.no/SKWS3Index/ssr/sok?navn=%QUERY*&epsgKode=4326&antPerSide=10',
dataType: 'xml',
filter: function(xml) {
var result = [];
$(xml).find('sokRes > stedsnavn').each(function(){
result.push({
value: $(this).find('stedsnavn').text()
,tokens: [$(this).find('stedsnavn').text()]
,lat: $(this).find('nord').text()
,lng: $(this).find('aust').text()
});
});
return result;
}
}
});
$('#ssr-search').on('typeahead:selected', function(e, object) {
var ll = new L.LatLng(object.lat, object.lng);
map.panTo(ll);
$('#ssr-search').val('');
})
});
}).call(this);

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

View file

@ -0,0 +1,44 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Routing in Leaflet</title>
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="libs/leaflet/leaflet.ie.css" /><![endif]-->
<link rel="stylesheet" href="app.css" />
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-38558206-1']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
</head>
<body>
<div id="search">
<strong>Søk:</strong> <input type="text" id="ssr-search">
</div>
<div id="export">
<strong>tp_id</strong>: <input type="text" id="eta-id">
<button id="eta-export">Eksport</button>
<button id="eta-import">Import</button>
</div>
<div id="map"></div>
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="http://cdn.leafletjs.com/leaflet-0.7/leaflet.js"></script>
<script src="src/utils/LineUtil.Snapping.js"></script>
<script src="src/utils/Marker.Snapping.js"></script>
<script src="src/L.Routing.js"></script>
<script src="src/L.Routing.Storage.js"></script>
<script src="src/L.Routing.Draw.js"></script>
<script src="src/L.Routing.Edit.js"></script>
<script src="http://twitter.github.io/typeahead.js/releases/latest/typeahead.js"></script>
<script src="app.js"></script>
</body>
</html>

View file

@ -0,0 +1,286 @@
/*
* L.Routing.Draw class
*
* Responsible for drawing and contine drawing
*
* @dependencies L, L.Routing
*
* @usage new L.Routing.Draw(map, options);
*/
L.Routing.Draw = L.Handler.extend({
// INCLUDES
includes: [L.Mixin.Events]
// OPTIONS
,options: {}
/**
* Draw Constructor
*
* @access public
*
* @param <> parent - parent class instance
* @param <Oject> options - routing options
*
* @return void
*
* @todo fetch last waypoint
*/
,initialize: function (parent, options) {
this._parent = parent;
this._map = parent._map;
this._enabled = false;
L.Util.setOptions(this, options);
}
/**
* Enable drawing
*
* @access public
*
* @event map.routing:draw-start
* @event map.routing:draw-new
* @event map.routing:draw-continue
*
* @return void
*/
,enable: function() {
if (this._enabled) { return; }
this._enabled = true;
this._hidden = false;
this._dragging = false;
this._addHooks();
this.fire('enabled');
this._map.fire('routing:draw-start');
if (this._parent._segments._layers.length === 0) {
this._map.fire('routing:draw-new');
} else {
this._map.fire('routing:draw-continue');
}
}
/**
* Disable drawing
*
* @access public
*
* @event map.routing:draw-end
*
* @return void
*/
,disable: function() {
if (!this._enabled) { return; }
this._enabled = false;
this._removeHooks();
this.fire('disabled');
this._map.fire('routing:draw-end');
}
/**
* Add hooks
*
* @access private
*
* @return void
*
* @todo hide and style the trailer!
*/
,_addHooks: function() {
if (!this._map) { return; }
// Visible Marker
if (!this._marker) {
this._marker = new L.Marker(this._map.getCenter(), {
icon: this.options.icons.normal
,zIndexOffset: this.options.zIndexOffset
,clickable: false
});
}
// Trailing line
if (!this._trailer) {
var ll = this._map.getCenter();
this._trailer = new L.Polyline([ll, ll], {
opacity: 0.2
,clickable: false
});
}
this._parent.on('waypoint:mouseover', this._catchWaypointEvent, this);
this._parent.on('waypoint:mouseout' , this._catchWaypointEvent, this);
this._parent.on('waypoint:dragstart', this._catchWaypointEvent, this);
this._parent.on('waypoint:dragend' , this._catchWaypointEvent, this);
this._parent.on('segment:mouseover' , this._catchWaypointEvent, this);
this._parent.on('segment:mouseout' , this._catchWaypointEvent, this);
this._parent.on('segment:dragstart' , this._catchWaypointEvent, this);
this._parent.on('segment:dragend' , this._catchWaypointEvent, this);
this._map.on('mousemove', this._onMouseMove, this);
this._map.on('click', this._onMouseClick, this);
this._marker.addTo(this._map);
this._trailer.addTo(this._map);
}
/**
* Remove hooks
*
* This method is invoked after the `disable()` has been called and removes
* all the hooks set up using the `_addHooks()` method.
*
* @access private
*
* @return void
*/
,_removeHooks: function() {
if (!this._map) { return; }
this._parent.off('waypoint:mouseover', this._catchWaypointEvent, this);
this._parent.off('waypoint:mouseout' , this._catchWaypointEvent, this);
this._parent.off('waypoint:dragstart', this._catchWaypointEvent, this);
this._parent.off('waypoint:dragend' , this._catchWaypointEvent, this);
this._parent.off('segment:mouseover' , this._catchWaypointEvent, this);
this._parent.off('segment:mouseout' , this._catchWaypointEvent, this);
this._parent.off('segment:dragstart' , this._catchWaypointEvent, this);
this._parent.off('segment:dragend' , this._catchWaypointEvent, this);
this._map.off('click', this._onMouseClick, this);
this._map.off('mousemove', this._onMouseMove, this);
this._map.removeLayer(this._marker);
this._map.removeLayer(this._trailer);
delete this._marker;
delete this._trailer;
}
/**
* Handle waypoint events
*
* @access private
*
* @param <L.Event> e - waypoint event
*
* @return void
*/
,_catchWaypointEvent: function(e) {
var type = e.type.split(':')[1];
if (this._hidden) {
if (this._dragging) {
if (type === 'dragend') {
this._dragging = false;
}
} else {
if (type === 'mouseout') {
this._show();
} else if (type === 'dragstart') {
this._dragging = true;
}
}
} else {
if (type === 'mouseover') {
this._hide();
}
}
}
/**
* Hide HUD
*
* Call this method in order to quickly hide graphical drawing elements for
* instance hoovering over draggable objects which should tempoarily disable
* dragging.
*
* @access private
*
* @return void
*/
,_hide: function() {
this._hidden = true;
this._marker.setOpacity(0.0);
this._trailer.setStyle({opacity: 0.0});
}
/**
* Show HUD
*
* Call this method to restore graphical drawing elements after they have been
* hidden.
*
* @access private
*
* @return void
*/
,_show: function() {
this._hidden = false;
this._marker.setOpacity(1.0);
this._trailer.setStyle({opacity: 0.2});
}
/**
* Mouse move handler
*
* @access private
*
* @param <L.Event> e - mouse move event
*
* @return void
*/
,_onMouseMove : function(e) {
if (this._hidden) { return; }
var latlng = e.latlng;
var last = this._parent.getLast();
if (this.options.snapping) {
latlng = L.LineUtil.snapToLayers(latlng, null, this.options.snapping);
}
this._marker.setLatLng(latlng);
if (last !== null) {
this._trailer.setLatLngs([last.getLatLng(), latlng]);
};
}
/**
* Mouse click handler
*
* @access private
*
* @param <L.Event> e - mouse click event
*
* @event map.routing:new-waypoint
*
* @return void
*/
,_onMouseClick: function(e) {
if (this._hidden) { return; }
var marker, latlng, last;
latlng = e.latlng;
if (this.options.snapping) {
latlng = L.LineUtil.snapToLayers(latlng, null, this.options.snapping);
}
marker = new L.Marker(latlng);
last = this._parent.getLast();
this._trailer.setLatLngs([latlng, latlng]);
this._parent.addWaypoint(marker, last, null, function(err, data) {
// console.log(err, data);
});
}
});

View file

@ -0,0 +1,387 @@
/*
* L.Routing.Edit class
*
* Responsible handle edits
*
* @dependencies L, L.Routing
*
* @usage new L.Routing.Draw(map, options);
*/
L.Routing.Edit = L.Handler.extend({
// INCLUDES
includes: [L.Mixin.Events]
// OPTIONS
,options: {}
/**
* Edit Constructor
*
* @access public
*
* @param <> parent - parent class instance
* @param <Oject> options - routing options
*
* @return void
*
* @todo fetch last waypoint
*/
,initialize: function (parent, options) {
this._parent = parent;
this._map = parent._map;
this._enabled = false;
L.Util.setOptions(this, options);
}
/**
* Enable drawing
*
* @access public
*
* @event map.routing:edit-start
*
* @return void
*/
,enable: function() {
if (this._enabled) { return; }
this._enabled = true;
this._addHooks();
this.fire('enabled');
this._map.fire('routing:edit-start');
}
/**
* Disable drawing
*
* @access public
*
* @event map.draw:edit-end
*
* @return void
*/
,disable: function() {
if (!this._enabled) { return; }
this._enabled = false;
this._removeHooks();
this.fire('disabled');
this._map.fire('routing:edit-end');
}
/**
* Add hooks
*
* This method is invoked when `enable()` is called and sets up all
* necessary hooks such as:
* * text selection
* * key listeners
* * mouse marker
*
* @access private
*
* @return void
*
* @todo hide and style the trailer!
*/
,_addHooks: function() {
if (!this._map) { return; }
if (!this._mouseMarker) {
this._mouseMarker = L.marker(this._map.getCenter(), {
icon: L.divIcon({
className: 'line-mouse-marker'
,iconAnchor: [5, 5]
,iconSize: [10, 10]
})
,clickable: true
,draggable: true
,opacity: 0
,zIndexOffset: this.options.zIndexOffset
});
}
this._mouseMarker.addTo(this._map);
if (!this._trailer1) {
var ll = this._map.getCenter();
var style = {opacity: 0.0,clickable: false};
this._trailer1 = new L.Polyline([ll, ll], style);
this._trailer2 = new L.Polyline([ll, ll], style);
}
this._trailer1.addTo(this._map);
this._trailer2.addTo(this._map);
this._parent.on('segment:mouseover' , this._segmentOnMouseover, this);
this._mouseMarker.on('dragstart' , this._segmentOnDragstart, this);
this._mouseMarker.on('drag' , this._segmentOnDrag, this);
this._mouseMarker.on('dragend' , this._segmentOnDragend, this);
this._parent.on('waypoint:dragstart', this._waypointOnDragstart, this);
this._parent.on('waypoint:drag' , this._waypointOnDrag, this);
this._parent.on('waypoint:dragend' , this._waypointOnDragend, this);
}
/**
* Remove hooks
*
* This method is invoked after the `disable()` has been called and removes
* all the hooks set up using the `_addHooks()` method.
*
* @access private
*
* @return void
*/
,_removeHooks: function() {
if (!this._map) { return; }
// this._trailer1.addTo(this._map);
// this._trailer2.addTo(this._map);
this._parent.off('segment:mouseover' , this._segmentOnMouseover, this);
this._mouseMarker.off('dragstart' , this._segmentOnDragstart, this);
this._mouseMarker.off('drag' , this._segmentOnDrag, this);
this._mouseMarker.off('dragend' , this._segmentOnDragend, this);
this._parent.off('waypoint:dragstart', this._waypointOnDragstart, this);
this._parent.off('waypoint:drag' , this._waypointOnDrag, this);
this._parent.off('waypoint:dragend' , this._waypointOnDragend, this);
}
/**
* Fired when the mouse first enters a segment
*
* @access private
*
* @param <L.Event> e - mouse over event
*
* @return void
*/
,_segmentOnMouseover: function(e) {
this._mouseMarker.setOpacity(1.0);
this._map.on('mousemove', this._segmentOnMousemove, this);
}
/**
* Fired when the mouse leaves a segement
*
* @access private
*
* @param <L.Event> e - mouse move event
*
* @return void
*/
,_segmentOnMouseout: function(e) {
if (this._dragging) { return; }
this._mouseMarker.setOpacity(0.0);
this._map.off('mousemove', this._segmentOnMousemove, this);
this.fire('segment:mouseout');
}
/**
* Fired when the mouse is moved
*
* This method is fired continously when mouse is moved in edition mode.
*
* @access private
*
* @param <L.Event> e - mouse move event
*
* @return void
*/
,_segmentOnMousemove: function(e) {
if (this._dragging) { return; }
var latlng = L.LineUtil.snapToLayers(e.latlng, null, {
layers: [this._parent._segments]
,sensitivity: 40
,vertexonly: false
});
if (latlng._feature === null) {
this._segmentOnMouseout(e);
} else {
this._mouseMarker._snapping = latlng._feature._routing;
this._mouseMarker.setLatLng(latlng);
}
}
/**
* Mouse marker dragstart
*
* @access private
*
* @param <L.Event> e - mouse dragstart event
*
* @return void
*/
,_segmentOnDragstart: function(e) {
var latlng = e.target.getLatLng();
var next = e.target._snapping.nextMarker;
var prev = e.target._snapping.prevMarker;
this._setTrailers(latlng, next, prev, true);
this._dragging = true;
this.fire('segment:dragstart');
}
/**
* Fired when a marker is dragged
*
* This method is fired continously when dragging a marker and snapps the
* marker to the snapping layer.
*
* @access private
*
* @param <L.Event> e - mouse drag event
*
* @return void
*/
,_segmentOnDrag: function(e) {
var latlng = e.target.getLatLng();
var next = e.target._snapping.nextMarker;
var prev = e.target._snapping.prevMarker;
if (this.options.snapping) {
latlng = L.LineUtil.snapToLayers(latlng, null, this.options.snapping);
}
e.target.setLatLng(latlng);
this._setTrailers(latlng, next, prev);
}
/**
* Mouse marker dragend
*
* @access private
*
* @param <L.Event> e - mouse dragend event
*
* @return void
*/
,_segmentOnDragend: function(e) {
var next = this._mouseMarker._snapping.nextMarker;
var prev = this._mouseMarker._snapping.prevMarker;
var latlng = this._mouseMarker.getLatLng();
this._parent.addWaypoint(latlng, prev, next, function(err, data) {
//console.log(err, data);
});
this._dragging = false;
this._setTrailers(null, null, null, false);
this.fire('segment:dragend');
}
/**
* Fired when marker drag start
*
* @access private
*
* @param <L.Event> e - mouse dragend event
*
* @return void
*/
,_waypointOnDragstart: function(e) {
var next = e.marker._routing.nextMarker;
var prev = e.marker._routing.prevMarker;
this._setTrailers(e.marker.getLatLng(), next, prev, true);
}
/**
* Fired while dragging marker
*
* @access private
*
* @access private
*
* @param <L.Event> e - mouse drag event
*
* @return void
*/
,_waypointOnDrag: function(e) {
var latlng = e.marker._latlng;
var next = e.marker._routing.nextMarker;
var prev = e.marker._routing.prevMarker;
if (this.options.snapping) {
latlng = L.LineUtil.snapToLayers(latlng, null, this.options.snapping);
}
e.marker.setLatLng(latlng);
this._setTrailers(latlng, next, prev);
}
/**
* Fired when marker drag ends
*
* @access private
*
* @param <L.Event> e - mouse dragend event
*
* @return void
*/
,_waypointOnDragend: function(e) {
this._setTrailers(null, null, null, false);
this._parent.routeWaypoint(e.marker, function(err, data) {
//console.log('_waypointOnDragend.cb', err, data);
});
}
/**
* Fired when marker is clicked
*
* This method is fired when a marker is clicked by the user. It will then
* procede to remove the marker and reroute any connected line segments.
*
* @access private
*
* @param <L.Event> e - mouse click event
*
* @return void
*/
,_waypointOnClick: function(e) {
this._parent.removeWaypoint(e.layer, function(err, data) {
//console.log('_waypointOnDragend.cb', err, data);
});
}
/**
* Set trailing guide lines
*
*/
,_setTrailers: function(latlng, next, prev, show) {
if (typeof show !== 'undefined') {
if (show === false) {
this._trailer1.setStyle({opacity: 0.0});
this._trailer2.setStyle({opacity: 0.0});
return;
} else {
if (next !== null) {
this._trailer1.setStyle({opacity: 0.2});
}
if (prev !== null) {
this._trailer2.setStyle({opacity: 0.2});
}
}
}
if (next) {
this._trailer1.setLatLngs([latlng, next.getLatLng()]);
}
if (prev) {
this._trailer2.setLatLngs([latlng, prev.getLatLng()]);
}
}
});

View file

@ -0,0 +1,31 @@
/*
* Leaflet Routing Storage
*
* Storing routable objects
*
* @dependencies L, L.Routing
*
* @usage new L.Routing(options);
*/
(function () {
L.Routing.Storage = L.MultiPolyline.extend({
/**
* Class constructor
*/
initialize: function (latlngs, options) {
this._layers = {};
this._options = options;
this.setLatLngs(latlngs);
this.on('layeradd', function() {
console.log('layeradd', arguments);
}, this);
}
});
L.Routing.storage = function (latlngs, options) {
return new L.MultiPolyline(latlngs, options);
};
}());

View file

@ -0,0 +1,556 @@
/*
* L.Routing main class
*
* Main clase for the Leaflet routing module
*
* @dependencies L
*
* @usage new L.Routing(options);
*
* @todo use L.Class.extend instead?
*/
L.Routing = L.Control.extend({
// INCLUDES
includes: [L.Mixin.Events]
// CONSTANTS
,statics: {
VERSION: '0.0.2-dev'
}
// OPTIONS
,options: {
position: 'topleft'
,icons: {
start: new L.Icon.Default()
,end: new L.Icon.Default()
,normal: new L.Icon.Default()
}
,zIndexOffset: 2000
,routing: {
router: null // function (<L.Latlng> l1, <L.Latlng> l2, <Function> cb)
}
,snapping: {
layers: [] // layers to snap to
,sensitivity: 10 // snapping sensitivity
,vertexonly: false // vertex only snapping
}
}
/**
* Routing Constructor
*
* @access public
*
* @param <Object> options - non-default options
*
* @todo render display of segments and waypoints
*/
,initialize: function (options) {
this._editing = false;
this._drawing = false;
L.Util.setOptions(this, options);
}
/**
* Called when controller is added to map
*
* @access public
*
* @param <L.Map> map - map instance
*
* @return <HTMLElement> container
*/
,onAdd: function (map) {
this._map = map;
this._container = this._map._container;
this._overlayPane = this._map._panes.overlayPane;
this._popupPane = this._map._panes.popupPane;
this._router = this.options.routing.router;
this._segments = new L.FeatureGroup().addTo(map);
this._waypoints = new L.FeatureGroup().addTo(map);
this._waypoints._first = null;
this._waypoints._last = null;
//L.DomUtil.disableTextSelection();
//this._tooltip = new L.Tooltip(this._map);
//this._tooltip.updateContent({ text: L.drawLocal.draw.marker.tooltip.start });
L.DomEvent.addListener(this._container, 'keyup', this._keyupListener, this);
this._draw = new L.Routing.Draw(this, {
icons: this.options.icons
,zIndexOffset: this.options.zIndexOffset
,snapping: this.options.snapping
});
this._edit = new L.Routing.Edit(this, {
icons: this.options.icons
,zIndexOffset: this.options.zIndexOffset
,snapping: this.options.snapping
});
this._edit.enable();
this.on('waypoint:click', this._waypointClickHandler, this)
this._segments.on('mouseover' , this._fireSegmentEvent, this);
this._edit.on('segment:mouseout' , this._fireSegmentEvent, this);
this._edit.on('segment:dragstart', this._fireSegmentEvent, this);
this._edit.on('segment:dragend' , this._fireSegmentEvent, this);
var container = L.DomUtil.create('div', 'leaflet-routing');
return container;
}
/**
* Called when controller is removed from map
*
* @access public
*
* @param <L.Map> map - map instance
*/
,onRemove: function(map) {
//L.DomUtil.create('div', 'leaflet-routing'); <= delete this
this.off('waypoint:click', this._waypointClickHandler, this)
this._segments.off('mouseover' , this._fireSegmentEvent, this);
this._edit.off('segment:mouseout' , this._fireSegmentEvent, this);
this._edit.off('segment:dragstart', this._fireSegmentEvent, this);
this._edit.off('segment:dragend' , this._fireSegmentEvent, this);
this._edit.disable();
this._draw.disable();
L.DomUtil.enableTextSelection();
// this._tooltip.dispose();
// this._tooltip = null;
L.DomEvent.removeListener(this._container, 'keyup', this._keyupListener);
delete this._draw;
delete this._edit;
delete this._map;
delete this._router;
delete this._segments;
delete this._waypoints;
delete this.options;
}
,_waypointClickHandler: function(e) {
this.removeWaypoint(e.marker, function() {
console.log(arguments);
});
}
/**
* Add new waypoint to path
*
* @access public
*
* @param <L.Marker> marker - new waypoint marker (can be ll)
* @param <L.Marker> prev - previous waypoint marker
* @param <L.Marker> next - next waypoint marker
* @param <Function> cb - callback method
*
* @return void
*/
,addWaypoint: function(marker, prev, next, cb) {
if (marker instanceof L.LatLng) {
marker = new L.Marker(marker);
}
marker._routing = {
prevMarker : prev
,nextMarker : next
,prevLine : null
,nextLine : null
,timeoutID : null
};
if (this._waypoints._first === null && this._waypoints._last === null) {
this._waypoints._first = marker;
this._waypoints._last = marker;
} else if (next === null) {
this._waypoints._last = marker;
} else if (prev === null) {
this._waypoints._first = marker;
}
if (marker._routing.prevMarker !== null) {
marker._routing.prevMarker._routing.nextMarker = marker;
marker._routing.prevLine = marker._routing.prevMarker._routing.nextLine;
if (marker._routing.prevLine !== null) {
marker._routing.prevLine._routing.nextMarker = marker;
}
}
if (marker._routing.nextMarker !== null) {
marker._routing.nextMarker._routing.prevMarker = marker;
marker.nextLine = marker._routing.nextMarker._routing.prevLine;
if (marker._routing.nextLine !== null) {
marker._routing.nextLine._routing.prevMarker = marker;
}
}
marker.on('mouseover', this._fireWaypointEvent, this);
marker.on('mouseout' , this._fireWaypointEvent, this);
marker.on('dragstart', this._fireWaypointEvent, this);
marker.on('dragend' , this._fireWaypointEvent, this);
marker.on('drag' , this._fireWaypointEvent, this);
marker.on('click' , this._fireWaypointEvent, this);
this.routeWaypoint(marker, cb);
this._waypoints.addLayer(marker);
marker.dragging.enable();
}
/**
* Remove a waypoint from path
*
* @access public
*
* @param <L.Marker> marker - new waypoint marker (can be ll)
* @param <Function> cb - callback method
*
* @return void
*/
,removeWaypoint: function(marker, cb) {
marker.off('mouseover', this._fireWaypointEvent, this);
marker.off('mouseout' , this._fireWaypointEvent, this);
marker.off('dragstart', this._fireWaypointEvent, this);
marker.off('dragend' , this._fireWaypointEvent, this);
marker.off('drag' , this._fireWaypointEvent, this);
marker.off('click' , this._fireWaypointEvent, this);
var prev = marker._routing.prevMarker;
var next = marker._routing.nextMarker;
if (this._waypoints._first && marker._leaflet_id === this._waypoints._first._leaflet_id) {
this._waypoints._first = next;
}
if (this._waypoints._last && marker._leaflet_id === this._waypoints._last._leaflet_id) {
this._waypoints._last = prev;
}
if (prev !== null) {
prev._routing.nextMarker = next;
prev._routing.nextLine = null;
}
if (next !== null) {
next._routing.prevMarker = prev;
next._routing.prevLine = null;
}
if (marker._routing.nextLine !== null) {
this._segments.removeLayer(marker._routing.nextLine);
}
if (marker._routing.prevLine !== null) {
this._segments.removeLayer(marker._routing.prevLine);
}
this._waypoints.removeLayer(marker);
if (prev !== null) {
this.routeWaypoint(prev, cb);
} else if (next !== null) {
this.routeWaypoint(next, cb);
} else {
this._draw.enable();
cb(null, null);
}
}
/**
* Route with respect to waypoint
*
* @access public
*
* @param <L.Marker> marker - marker to route on
* @param <Function> cb - callback function
*
* @return void
*
* @todo add propper error checking for callback
*/
,routeWaypoint: function(marker, cb) {
var i = 0;
var $this = this;
var callback = function(err, data) {
i++;
if (i === 2) {
$this.fire('routing:routeWaypointEnd');
cb(err, marker);
}
}
this.fire('routing:routeWaypointStart');
this._routeSegment(marker._routing.prevMarker, marker, callback);
this._routeSegment(marker, marker._routing.nextMarker, callback);
}
/**
* Route segment between two markers
*
* @access private
*
* @param <L.Marker> m1 - first waypoint marker
* @param <L.Marker> m2 - second waypoint marker
* @param <Function> cb - callback function (<Error> err, <String> data)
*
* @return void
*
* @todo logic if router fails
*/
,_routeSegment: function(m1, m2, cb) {
var $this = this;
if (m1 === null || m2 === null) {
return cb(null, true);
}
this._router(m1.getLatLng(), m2.getLatLng(), function(err, layer) {
if (typeof layer === 'undefined') {
var layer = new L.Polyline([m1.getLatLng(), m2.getLatLng()]);
}
layer._routing = {
prevMarker: m1
,nextMarker: m2
};
if (m1._routing.nextLine !== null) {
$this._segments.removeLayer(m1._routing.nextLine);
}
$this._segments.addLayer(layer);
m1._routing.nextLine = layer;
m2._routing.prevLine = layer;
return cb(null, layer);
});
}
/**
* Iterate over all segments and execute callback for each segment
*
* @access private
*
* @param <function> callback - function to call for each segment
* @param <object> context - callback execution context (this). Optional, default: this
*
* @return void
*/
,_eachSegment: function(callback, context) {
var thisArg = context || this;
var marker = this.getFirst();
if (marker === null) { return; }
while (marker._routing.nextMarker !== null) {
var m1 = marker;
var m2 = marker._routing.nextMarker;
var line = marker._routing.nextLine;
callback.call(thisArg, m1, m2, line);
marker = marker._routing.nextMarker;
}
}
/**
* Fire events
*
* @access private
*
* @param <L.Event> e - mouse event
*
* @return void
*/
,_fireWaypointEvent: function(e) {
this.fire('waypoint:' + e.type, {marker:e.target});
}
/**
*
*/
,_fireSegmentEvent: function(e) {
if (e.type.split(':').length === 2) {
this.fire(e.type);
} else {
this.fire('segment:' + e.type);
}
}
/**
* Get first waypoint
*
* @access public
*
* @return L.Marker
*/
,getFirst: function() {
return this._waypoints._first;
}
/**
* Get last waypoint
*
* @access public
*
* @return L.Marker
*/
,getLast: function() {
return this._waypoints._last;
}
/**
* Get all waypoints
*
* @access public
*
* @return <L.LatLng[]> all waypoints or empty array if none
*/
,getWaypoints: function() {
var latLngs = [];
this._eachSegment(function(m1) {
latLngs.push(m1.getLatLng());
});
if (this.getLast()) {
latLngs.push(this.getLast().getLatLng());
}
return latLngs;
}
/**
* Concatenates all route segments to a single polyline
*
* @access public
*
* @return <L.Polyline> polyline, with empty _latlngs when no route segments
*/
,toPolyline: function() {
var latLngs = [];
this._eachSegment(function(m1, m2, line) {
latLngs = latLngs.concat(line.getLatLngs());
});
return L.polyline(latLngs);
}
/**
* Export route to GeoJSON
*
* @access public
*
* @param <boolean> enforce2d - enforce 2DGeoJSON
*
* @return <object> GeoJSON object
*
*/
,toGeoJSON: function(enforce2d) {
var geojson = {type: "LineString", properties: {waypoints: []}, coordinates: []};
var current = this._waypoints._first;
if (current === null) { return geojson; }
geojson.properties.waypoints.push([current.getLatLng().lng, current.getLatLng().lat]);
while (current._routing.nextMarker) {
var next = current._routing.nextMarker
geojson.properties.waypoints.push([next.getLatLng().lng, next.getLatLng().lat]);
var tmp = current._routing.nextLine.getLatLngs();
for (var i = 0; i < tmp.length; i++) {
if (tmp[i].alt && (typeof enforce2d === 'undefined' || enforce2d === false)) {
geojson.coordinates.push([tmp[i].lat, tmp[i].lng, tmp[i].alt]);
} else {
geojson.coordinates.push([tmp[i].lat, tmp[i].lng]);
}
}
current = current._routing.nextMarker;
}
return geojson
}
/**
* Start (or continue) drawing
*
* Call this method in order to start or continue drawing. The drawing handler
* will be activate and the user can draw on the map.
*
* @access public
*
* @return void
*
* @todo check enable
*/
,draw: function (enable) {
if (typeof enable === 'undefined') {
var enable = true;
}
if (enable) {
this._draw.enable();
} else {
this._draw.disable();
}
}
/**
* Enable or disable routing
*
* @access public
*
* @return void
*
* @todo check enable
*/
,routing: function (enable) {
throw new Error('Not implemented');
}
/**
* Enable or disable snapping
*
* @access public
*
* @return void
*
* @todo check enable
*/
,snapping: function (enable) {
throw new Error('Not implemented');
}
/**
* Key up listener
*
* * `ESC` to cancel drawing
* * `M` to enable drawing
*
* @access private
*
* @return void
*/
,_keyupListener: function (e) {
if (e.keyCode === 27) {
this._draw.disable();
} else if (e.keyCode === 77) {
this._draw.enable();
}
}
});

View file

@ -0,0 +1,186 @@
L.Util.extend(L.LineUtil, {
/**
* Snap to all layers
*
* @param <Latlng> latlng - original position
* @param <Number> id - leaflet unique id
* @param <Object> opts - snapping options
*
* @return <Latlng> closest point
*/
snapToLayers: function (latlng, id, opts) {
var i, j, keys, feature, res, sensitivity, vertexonly, layers, minDist, minPoint, map;
sensitivity = opts.sensitivity || 10;
vertexonly = opts.vertexonly || false;
layers = opts.layers || [];
minDist = Infinity;
minPoint = latlng;
minPoint._feature = null; // containing layer
map = opts.layers[0]._map; // @todo check for undef
for (i = 0; i < opts.layers.length; i++) {
keys = Object.keys(opts.layers[i]._layers);
for (j = 0; j < keys.length; j++) {
feature = opts.layers[i]._layers[keys[j]];
// Don't even try snapping to itself!
if (id === feature._leaflet_id) { continue; }
// GeometryCollection
if (feature._layers) {
var newLatlng = this.snapToLayers(latlng, id, {
'sensitivity': sensitivity,
'vertexonly': vertexonly,
'layers': [feature]
});
// What if this is the same?
res = {'minDist': latlng.distanceTo(newLatlng), 'minPoint': newLatlng};
// Marker
} else if (feature instanceof L.Marker) {
res = this._snapToLatlngs(latlng, [feature.getLatLng()], map, sensitivity, vertexonly, minDist);
// Polyline
} else if (feature instanceof L.Polyline) {
res = this._snapToLatlngs(latlng, feature.getLatLngs(), map, sensitivity, vertexonly, minDist);
// MultiPolyline
} else if (feature instanceof L.MultiPolyline) {
console.error('Snapping to MultiPolyline is currently unsupported', feature);
res = {'minDist': minDist, 'minPoint': minPoint};
// Polygon
} else if (feature instanceof L.Polygon) {
res = this._snapToPolygon(latlng, feature, map, sensitivity, vertexonly, minDist);
// MultiPolygon
} else if (feature instanceof L.MultiPolygon) {
res = this._snapToMultiPolygon(latlng, feature, map, sensitivity, vertexonly, minDist);
// Unknown
} else {
console.error('Unsupported snapping feature', feature);
res = {'minDist': minDist, 'minPoint': minPoint};
}
if (res.minDist < minDist) {
minDist = res.minDist;
minPoint = res.minPoint;
minPoint._feature = feature;
}
}
}
return minPoint;
},
/**
* Snap to Polygon
*
* @param <Latlng> latlng - original position
* @param <L.Polygon> feature -
* @param <L.Map> map -
* @param <Number> sensitivity -
* @param <Boolean> vertexonly -
* @param <Number> minDist -
*
* @return <Object> minDist and minPoint
*/
_snapToPolygon: function (latlng, polygon, map, sensitivity, vertexonly, minDist) {
var res, keys, latlngs, i, minPoint;
minPoint = null;
latlngs = polygon.getLatLngs();
latlngs.push(latlngs[0]);
res = this._snapToLatlngs(latlng, polygon.getLatLngs(), map, sensitivity, vertexonly, minDist);
if (res.minDist < minDist) {
minDist = res.minDist;
minPoint = res.minPoint;
}
keys = Object.keys(polygon._holes);
for (i = 0; i < keys.length; i++) {
latlngs = polygon._holes[keys[i]];
latlngs.push(latlngs[0]);
res = this._snapToLatlngs(latlng, polygon._holes[keys[i]], map, sensitivity, vertexonly, minDist);
if (res.minDist < minDist) {
minDist = res.minDist;
minPoint = res.minPoint;
}
}
return {'minDist': minDist, 'minPoint': minPoint};
},
/**
* Snap to MultiPolygon
*
* @param <Latlng> latlng - original position
* @param <L.Polygon> feature -
* @param <L.Map> map -
* @param <Number> sensitivity -
* @param <Boolean> vertexonly -
* @param <Number> minDist -
*
* @return <Object> minDist and minPoint
*/
_snapToMultiPolygon: function (latlng, multipolygon, map, sensitivity, vertexonly, minDist) {
var i, keys, res, minPoint;
minPoint = null;
keys = Object.keys(multipolygon._layers);
for (i = 0; i < keys.length; i++) {
res = this._snapToPolygon(latlng, multipolygon._layers[keys[i]], map, sensitivity, vertexonly, minDist);
if (res.minDist < minDist) {
minDist = res.minDist;
minPoint = res.minPoint;
}
}
return {'minDist': minDist, 'minPoint': minPoint};
},
/**
* Snap to <Array> of <Latlang>
*
* @param <LatLng> latlng - cursor click
* @param <Array> latlngs - array of <L.LatLngs> to snap to
* @param <Object> opts - snapping options
* @param <Boolean> isPolygon - if feature is a polygon
*
* @return <Object> minDist and minPoint
*/
_snapToLatlngs: function (latlng, latlngs, map, sensitivity, vertexonly, minDist) {
var i, tmpDist, minPoint, p, p1, p2, d2;
p = map.latLngToLayerPoint(latlng);
p1 = minPoint = null;
for (i = 0; i < latlngs.length; i++) {
p2 = map.latLngToLayerPoint(latlngs[i]);
if (!vertexonly && p1 !== null) {
tmpDist = L.LineUtil.pointToSegmentDistance(p, p1, p2);
if (tmpDist < minDist && tmpDist <= sensitivity) {
minDist = tmpDist;
minPoint = map.layerPointToLatLng(L.LineUtil.closestPointOnSegment(p, p1, p2));
}
} else if ((d2 = p.distanceTo(p2)) && d2 <= sensitivity && d2 < minDist) {
minDist = d2;
minPoint = latlngs[i];
}
p1 = p2;
}
return {'minDist': minDist, 'minPoint': minPoint};
}
});

View file

@ -0,0 +1,12 @@
L.Marker.include({
/**
* Snap to function
*
* @param <LatLng> latlng - original position
*
* @return <LatLng> - new position
*/
snapTo: function (latlng) {
return L.LineUtil.snapToLayers(latlng, this._leaflet_id, this.options.snapping);
}
});

View file

@ -0,0 +1,12 @@
L.Polyline.include({
/**
* Snap to function
*
* @param <LatLng> latlng - original position
*
* @return <LatLng> - new position
*/
snapTo: function (latlng) {
return L.LineUtil.snapToLayers(latlng, this._leaflet_id, this.options.snapping);
}
});

View file

@ -0,0 +1,22 @@
{
"name": "leaflet-search",
"version": "1.4.7",
"main": "leaflet-search.js",
"ignore": [
"**/.*",
"node_modules",
"components",
"bower_components",
"examples"
],
"homepage": "https://github.com/stefanocudini/leaflet-search",
"_release": "1.4.7",
"_resolution": {
"type": "version",
"tag": "v1.4.7",
"commit": "411f216e1f407da9ef61047832b227c19ed47fab"
},
"_source": "git://github.com/stefanocudini/leaflet-search.git",
"_target": "*",
"_originalSource": "leaflet-search"
}

12
bower_components/leaflet-search/BUGS vendored Normal file
View file

@ -0,0 +1,12 @@
. option condition problem {autoCollapse: true, markerLocation: true} not show location, row 61
. option condition problem {autoCollapse:false }, row 62
. problem with jsonp/ajax when remote filter has different behavior of this._filterRecords, row 322
. _handleAutoresize Should resize max search box size when map is resized., row 616
. if collapse in _handleSubmit hide _markerLoc!, row 684
. autoCollapse option hide this._markerLoc before that visualized!!, row 714

115
bower_components/leaflet-search/Gruntfile.js vendored Executable file
View file

@ -0,0 +1,115 @@
'use strict';
module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-cssmin');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-todos');
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
meta: {
banner:
'/* \n'+
' * Leaflet Search Control v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %> \n'+
' * \n'+
' * Copyright 2014 <%= pkg.author.name %> \n'+
' * <%= pkg.author.email %> \n'+
' * <%= pkg.author.url %> \n'+
' * \n'+
' * Licensed under the <%= pkg.license %> license. \n'+
' * \n'+
' * Demo: \n'+
' * <%= pkg.homepage %> \n'+
' * \n'+
' * Source: \n'+
' * <%= pkg.repository.url %> \n'+
' * \n'+
' */\n'
},
clean: {
dist: {
src: ['dist/*']
}
},
jshint: {
options: {
globals: {
console: true,
module: true
},
"-W099": true, //ignora tabs e space warning
"-W033": true,
"-W044": true //ignore regexp
},
files: ['src/*.js']
},
concat: {
//TODO cut out SearchMarker
options: {
banner: '<%= meta.banner %>'
},
dist: {
files: {
'dist/leaflet-search.src.js': ['src/leaflet-search.js'],
'dist/leaflet-search.src.css': ['src/leaflet-search.css'],
'dist/leaflet-search.mobile.src.css': ['src/leaflet-search.mobile.css']
}
}
},
uglify: {
options: {
banner: '<%= meta.banner %>'
},
dist: {
files: {
'dist/leaflet-search.min.js': ['dist/leaflet-search.src.js']
}
}
},
cssmin: {
combine: {
files: {
'dist/leaflet-search.min.css': ['src/leaflet-search.css'],
'dist/leaflet-search.mobile.min.css': ['src/leaflet-search.mobile.css']
}
},
options: {
banner: '<%= meta.banner %>'
},
minify: {
expand: true,
cwd: 'dist/',
files: {
'dist/leaflet-search.min.css': ['src/leaflet-search.css'],
'dist/leaflet-search.mobile.min.css': ['src/leaflet-search.mobile.css']
}
}
},
todos: {
options: { verbose: false },
TODO: ['src/*.js'],
},
watch: {
dist: {
options: { livereload: true },
files: ['src/*'],
tasks: ['clean','concat','cssmin','jshint']
}
}
});
grunt.registerTask('default', [
'clean',
'concat',
'cssmin',
'jshint',
'uglify',
'todos'
]);
};

View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2013 Stefano Cudini
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View file

@ -0,0 +1,65 @@
Leaflet.Control.Search
============
#What
A leaflet control that search markers/features location by cutstom property.
With ajax/jsonp autocompletion and json fields re-mapping
Tested in Leaflet 0.6.4
#Where
**Demos:**
[labs.easyblog.it/maps/leaflet-search](http://labs.easyblog.it/maps/leaflet-search/)
**Source code:**
[Github](https://github.com/stefanocudini/leaflet-search)
[Bitbucket](https://bitbucket.org/zakis_/leaflet-search)
[NPM](https://npmjs.org/package/leaflet-search)
[Atmosphere](https://atmosphere.meteor.com/package/leaflet-search)
#How
Insert leaflet-search.css styles to your css page
Adding the search control to the map:
```
map.addControl( new L.Control.Search({layer: searchLayer}) );
//searchLayer if a L.LayerGroup contains searched markers
```
short way:
```
var map = new L.Map('map', { searchControl: {layer: searchLayer} });
```
other examples:
```
//ajax request to search.php for retrieve elements locations
map.addControl( new L.Control.Search({url: 'search.php?q={s}'}) );
//jsonp request to 3rd party service, implements Geocode Searching using OSM API
map.addControl( new L.Control.Search({
url: 'http://nominatim.openstreetmap.org/search?format=json&q={s}',
jsonpParam: 'json_callback',
propertyName: 'display_name',
propertyLoc: ['lat','lon']
}) );
//geojson layer, search and color feature vector
var searchControl = new L.Control.Search({layer: geojsonLayer, circleLocation:false});
searchControl.on('search_locationfound', function(e) {
e.layer.setStyle({fillColor: '#3f0'});
}).on('search_collapsed', function(e) {
featuresLayer.eachLayer(function(layer) {
featuresLayer.resetStyle(layer);
});
});
map.addControl(searchControl);
```

27
bower_components/leaflet-search/TODO vendored Normal file
View file

@ -0,0 +1,27 @@
Tasks found in: src/leaflet-search.js
[Line: 22] [low] //TODO important! implements uniq option 'sourceData' that recognizes source type: url,array,callback or layer
[Line: 23] [low] //TODO implement can do research on multiple sources
[Line: 26] [low] //TODO implement sub property filter for propertyName,propertyLoc like this: "prop.subprop.title"
[Line: 37] [low] //TODO add option for persist markerLoc after collapse!
[Line: 47] [low] //TODO add option collapsed, like control.layers
[Line: 296] [low] //TODO add option for case sesitive search, also showLocation
[Line: 299] [low] //TODO use .filter or .map
[Line: 361] [low] //TODO verify json[n].hasOwnProperty(propName)
[Line: 367] [low] //TODO remove script node after call run
[Line: 376] [low] //TODO add rnd param or randomize callback name! in recordsFromJsonp
[Line: 396] [low] //TODO add rnd param or randomize callback name! in recordsFromAjax
[Line: 458] [low] //TODO implements autype without selection(useful for mobile device)
[Line: 558] [low] //TODO important optimization!!! always append data in this._recordsCache
[Line: 562] [low] //TODO here insert function that search inputText FIRST in _recordsCache keys and if not find results..
[Line: 565] [low] //TODO change structure of _recordsCache
[Line: 616] [low] //TODO refact _handleAutoresize now is not accurate
[Line: 712] [low] //TODO showLocation: start animation after setView or panTo, maybe with map.on('moveend')...
[Line: 733] [low] //TODO add custom icon!
[Line: 741] [low] //TODO add inner circle
[Line: 797] [low] //TODO refact animate() more smooth! like this: http://goo.gl/DDlRs
[Line: 819] [low] //TODO use create event 'animateEnd' in SearchMarker
[Line: 49] [med] //FIXME option condition problem {autoCollapse: true, markerLocation: true} not show location
[Line: 50] [med] //FIXME option condition problem {autoCollapse: false }
[Line: 312] [med] //FIXME problem with jsonp/ajax when remote filter has different behavior of this._filterRecords
[Line: 685] [med] //FIXME if collapse in _handleSubmit hide _markerLoc!
[Line: 715] [med] //FIXME autoCollapse option hide this._markerLoc before that visualized!!

View file

@ -0,0 +1,12 @@
{
"name": "leaflet-search",
"version": "1.4.6",
"main": "leaflet-search.js",
"ignore": [
"**/.*",
"node_modules",
"components",
"bower_components",
"examples"
]
}

View file

@ -0,0 +1,18 @@
/*
* Leaflet Search Control v1.4.7 - 2014-01-04
*
* Copyright 2014 Stefano Cudini
* stefano.cudini@gmail.com
* http://labs.easyblog.it/
*
* Licensed under the MIT license.
*
* Demo:
* http://labs.easyblog.it/maps/leaflet-search/
*
* Source:
* git@github.com:stefanocudini/leaflet-search.git
*
*/
.leaflet-container .leaflet-control-search{position:relative;float:left;background:#fff;color:#1978cf;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;background-color:rgba(255,255,255,.8);z-index:1000;box-shadow:0 1px 7px rgba(0,0,0,.65);margin-left:10px;margin-top:10px}.leaflet-control-search.search-exp{box-shadow:0 1px 7px #999;background:#fff}.leaflet-control-search .search-input{display:block;float:left;background:#fff;border:1px solid #666;border-radius:2px;height:18px;padding:0 18px 0 2px;margin:3px 0 3px 3px}.leaflet-control-search.search-load .search-input{background:url(../images/loader.gif) no-repeat center right #fff}.leaflet-control-search.search-load .search-cancel{visibility:hidden}.leaflet-control-search .search-cancel{display:block;width:22px;height:18px;position:absolute;right:22px;margin:3px 0;background:url(../images/search-icon.png) no-repeat 0 -46px;text-decoration:none;filter:alpha(opacity=80);opacity:.8}.leaflet-control-search .search-cancel:hover{filter:alpha(opacity=100);opacity:1}.leaflet-control-search .search-cancel span{display:none;font-size:18px;line-height:20px;color:#ccc;font-weight:700}.leaflet-control-search .search-cancel:hover span{color:#aaa}.leaflet-control-search .search-button{display:block;float:left;width:26px;height:26px;background:url(../images/search-icon.png) no-repeat 2px 2px;border-radius:4px}.leaflet-control-search .search-button:hover{background:url(../images/search-icon.png) no-repeat 2px -22px}.leaflet-control-search .search-tooltip{position:absolute;top:100%;left:0;float:left;min-width:80px;max-height:106px;box-shadow:0 0 8px rgba(0,0,0,.4);-webkit-border-radius:5px;-webkit-border-top-left-radius:0;-moz-border-radius:5px;-moz-border-radius-topleft:0;border-radius:5px;border-top-left-radius:0;background-color:rgba(0,0,0,.25);z-index:1010;overflow-y:auto;overflow-x:hidden}.leaflet-control-search .search-tip{margin:2px;padding:2px;display:block;color:#000;background:#ddd;border-radius:.25em;text-decoration:none;white-space:nowrap;font-size:.85em;vertical-align:center}.leaflet-control-search .search-button:hover,.leaflet-control-search .search-tip-select,.leaflet-control-search .search-tip:hover{background-color:#fff}.leaflet-control-search .search-alert{cursor:pointer;clear:both;font-size:.75em;margin-bottom:5px;padding:0 .25em;color:#e00;font-weight:700;border-radius:.25em}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,18 @@
/*
* Leaflet Search Control v1.4.7 - 2014-01-04
*
* Copyright 2014 Stefano Cudini
* stefano.cudini@gmail.com
* http://labs.easyblog.it/
*
* Licensed under the MIT license.
*
* Demo:
* http://labs.easyblog.it/maps/leaflet-search/
*
* Source:
* git@github.com:stefanocudini/leaflet-search.git
*
*/
.leaflet-control.leaflet-control-search{z-index:2000}.leaflet-control-search .search-input{display:block;float:left;background:#fff;border:1px solid #666;border-radius:2px;height:24px;font-size:1.25em;padding:0 .125em;margin:3px;padding-right:30px}.leaflet-control-search .search-button,.leaflet-control-search .search-button:hover{background-image:url(../images/search-icon-mobile.png);-webkit-border-radius:4px;border-radius:4px;background-position:1px 1px;width:32px;height:32px}.leaflet-control-search.search-load .search-input{background:url(../images/loader.gif) no-repeat center right #fff}.leaflet-control-search .search-cancel{background-image:url(../images/search-icon-mobile.png);-webkit-border-radius:4px;border-radius:4px;background-position:0 -62px;width:26px;height:26px;right:34px;margin:3px}.leaflet-control-search .search-tooltip{max-height:142px}.leaflet-control-search .search-tip{font-size:1em;margin:2px;padding:2px;display:block;color:#000;background:rgba(255,255,255,.8);border-radius:.25em;text-decoration:none;white-space:nowrap;vertical-align:center}.leaflet-control-search .search-tip .climbo-icon-mini{float:right;display:block;white-space:nowrap}.leaflet-control-search .search-button:hover,.leaflet-control-search .search-tip-select,.leaflet-control-search .search-tip:hover{background-color:#fff}.leaflet-control-search .search-alert{font-size:1.2em}

View file

@ -0,0 +1,83 @@
/*
* Leaflet Search Control v1.4.7 - 2014-01-04
*
* Copyright 2014 Stefano Cudini
* stefano.cudini@gmail.com
* http://labs.easyblog.it/
*
* Licensed under the MIT license.
*
* Demo:
* http://labs.easyblog.it/maps/leaflet-search/
*
* Source:
* git@github.com:stefanocudini/leaflet-search.git
*
*/
/* SEARCH */
.leaflet-control.leaflet-control-search {
z-index:2000;
}
.leaflet-control-search .search-input {
display:block;
float:left;
background: #fff;
border:1px solid #666;
border-radius:2px;
height:24px;
font-size:1.25em;
padding:0 .125em;
margin:3px;
padding-right:30px;
}
.leaflet-control-search .search-button:hover,
.leaflet-control-search .search-button {
background-image: url('../images/search-icon-mobile.png');
-webkit-border-radius: 4px;
border-radius: 4px;
background-position: 1px 1px;
width:32px;
height:32px;
}
.leaflet-control-search.search-load .search-input {
background: url('../images/loader.gif') no-repeat center right #fff;
}
.leaflet-control-search .search-cancel {
background-image: url('../images/search-icon-mobile.png');
-webkit-border-radius: 4px;
border-radius: 4px;
background-position: 0px -62px;
width:26px;
height:26px;
right:34px;
margin:3px;
}
.leaflet-control-search .search-tooltip {
max-height:142px;/*(.search-tip height * 5)*/
}
.leaflet-control-search .search-tip {
font-size:1em;
margin:2px;
padding:2px;
display:block;
color:black;
background: rgba(255,255,255,0.8);
border-radius:.25em;
text-decoration:none;
white-space:nowrap;
vertical-align:center;
}
.leaflet-control-search .search-tip .climbo-icon-mini {
float:right;
display:block;
white-space:nowrap;
}
.leaflet-control-search .search-button:hover,
.leaflet-control-search .search-tip-select,
.leaflet-control-search .search-tip:hover {
background-color: #fff;
}
.leaflet-control-search .search-alert {
font-size:1.2em;
}

View file

@ -0,0 +1,137 @@
/*
* Leaflet Search Control v1.4.7 - 2014-01-04
*
* Copyright 2014 Stefano Cudini
* stefano.cudini@gmail.com
* http://labs.easyblog.it/
*
* Licensed under the MIT license.
*
* Demo:
* http://labs.easyblog.it/maps/leaflet-search/
*
* Source:
* git@github.com:stefanocudini/leaflet-search.git
*
*/
.leaflet-container .leaflet-control-search {
position:relative;
float:left;
background:#fff;
color:#1978cf;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
border-radius: 4px;
background-color: rgba(255, 255, 255, 0.8);
z-index:1000;
box-shadow: 0 1px 7px rgba(0,0,0,0.65);
margin-left: 10px;
margin-top: 10px;
}
.leaflet-control-search.search-exp {/*expanded*/
box-shadow: 0 1px 7px #999;
background: #fff;
}
.leaflet-control-search .search-input {
display:block;
float:left;
background: #fff;
border:1px solid #666;
border-radius:2px;
height:18px;
padding:0 18px 0 2px;
margin:3px 0 3px 3px;
}
.leaflet-control-search.search-load .search-input {
background: url('../images/loader.gif') no-repeat center right #fff;
}
.leaflet-control-search.search-load .search-cancel {
visibility:hidden;
}
.leaflet-control-search .search-cancel {
display:block;
width:22px;
height:18px;
position:absolute;
right:22px;
margin:3px 0;
background: url('../images/search-icon.png') no-repeat 0 -46px;
text-decoration:none;
filter: alpha(opacity=80);
opacity: 0.8;
}
.leaflet-control-search .search-cancel:hover {
filter: alpha(opacity=100);
opacity: 1;
}
.leaflet-control-search .search-cancel span {
display:none;/* comment for cancel button imageless */
font-size:18px;
line-height:20px;
color:#ccc;
font-weight:bold;
}
.leaflet-control-search .search-cancel:hover span {
color:#aaa;
}
.leaflet-control-search .search-button {
display:block;
float:left;
width:26px;
height:26px;
background: url('../images/search-icon.png') no-repeat 2px 2px;
border-radius:4px;
}
.leaflet-control-search .search-button:hover {
background: url('../images/search-icon.png') no-repeat 2px -22px;
}
.leaflet-control-search .search-tooltip {
position:absolute;
top:100%;
left:0;
float:left;
min-width:80px;
max-height:106px;/*(.search-tip height * 5)*/
box-shadow: 0 0 8px rgba(0,0,0,0.4);
-webkit-border-radius: 5px;
-webkit-border-top-left-radius: 0;
-moz-border-radius: 5px;
-moz-border-radius-topleft: 0;
border-radius: 5px;
border-top-left-radius: 0;
background-color: rgba(0, 0, 0, 0.25);
z-index:1010;
overflow-y:auto;
overflow-x:hidden;
}
.leaflet-control-search .search-tip {
font-size:.85em;
margin:2px;
padding:2px;
display:block;
color:black;
background: #ddd;
border-radius:.25em;
text-decoration:none;
white-space:nowrap;
font-size:.85em;
vertical-align:center;
}
.leaflet-control-search .search-tip-select,
.leaflet-control-search .search-tip:hover,
.leaflet-control-search .search-button:hover {
background-color: #fff;
}
.leaflet-control-search .search-alert {
cursor:pointer;
clear:both;
font-size:.75em;
margin-bottom:5px;
padding:0 .25em;
color:#e00;
font-weight:bold;
border-radius:.25em;
}

View file

@ -0,0 +1,855 @@
/*
* Leaflet Search Control v1.4.7 - 2014-01-04
*
* Copyright 2014 Stefano Cudini
* stefano.cudini@gmail.com
* http://labs.easyblog.it/
*
* Licensed under the MIT license.
*
* Demo:
* http://labs.easyblog.it/maps/leaflet-search/
*
* Source:
* git@github.com:stefanocudini/leaflet-search.git
*
*/
(function() {
L.Control.Search = L.Control.extend({
includes: L.Mixin.Events,
//
// Name Data passed Description
//
//Managed Events:
// search_locationfound {latlng, title} fired after moved and show markerLocation
// search_collapsed {} fired after control was collapsed
//
//Public methods:
// setLayer() L.LayerGroup() set layer search at runtime
// showAlert() 'Text message' Show alert message
//
options: {
url: '', //url for search by ajax request, ex: "search.php?q={s}"
jsonpParam: null, //jsonp param name for search by jsonp service, ex: "callback"
layer: null, //layer where search markers(is a L.LayerGroup)
callData: null, //function that fill _recordsCache, passed searching text by first param and callback in second
//TODO important! implements uniq option 'sourceData' that recognizes source type: url,array,callback or layer
//TODO implement can do research on multiple sources
propertyName: 'title', //property in marker.options(or feature.properties for vector layer) trough filter elements in layer
propertyLoc: 'loc', //field name for remapping location, using array: ['latname','lonname'] for select double fields(ex. ['lat','lon'] )
//TODO implement sub property filter for propertyName,propertyLoc like this: "prop.subprop.title"
callTip: null, //function that return row tip html node(or html string), receive text tooltip in first param
filterJSON: null, //callback for filtering data to _recordsCache
minLength: 1, //minimal text length for autocomplete
initial: true, //search elements only by initial text
autoType: true, //complete input with first suggested result and select this filled-in text.
delayType: 400, //delay while typing for show tooltip
tooltipLimit: -1, //limit max results to show in tooltip. -1 for no limit.
tipAutoSubmit: true, //auto map panTo when click on tooltip
autoResize: true, //autoresize on input change
autoCollapse: false, //collapse search control after submit(on button or on tips if enabled tipAutoSubmit)
//TODO add option for persist markerLoc after collapse!
autoCollapseTime: 1200, //delay for autoclosing alert and collapse after blur
animateLocation: true, //animate a circle over location found
circleLocation: true, //draw a circle in location found
markerLocation: false, //draw a marker in location found
zoom: null, //zoom after pan to location found, default: map.getZoom()
text: 'Search...', //placeholder value
textCancel: 'Cancel', //title in cancel button
textErr: 'Location not found', //error message
position: 'topleft'
//TODO add option collapsed, like control.layers
},
//FIXME option condition problem {autoCollapse: true, markerLocation: true} not show location
//FIXME option condition problem {autoCollapse: false }
initialize: function(options) {
L.Util.setOptions(this, options || {});
this._inputMinSize = this.options.text ? this.options.text.length : 10;
this._layer = this.options.layer || new L.LayerGroup();
this._filterJSON = this.options.filterJSON || this._defaultFilterJSON;
this._autoTypeTmp = this.options.autoType; //useful for disable autoType temporarily in delete/backspace keydown
this._countertips = 0; //number of tips items
this._recordsCache = {}; //key,value table! that store locations! format: key,latlng
},
onAdd: function (map) {
this._map = map;
this._container = L.DomUtil.create('div', 'leaflet-control-search');
this._input = this._createInput(this.options.text, 'search-input');
this._tooltip = this._createTooltip('search-tooltip');
this._cancel = this._createCancel(this.options.textCancel, 'search-cancel');
this._button = this._createButton(this.options.text, 'search-button');
this._alert = this._createAlert('search-alert');
if(this.options.circleLocation || this.options.markerLocation)
this._markerLoc = new SearchMarker([0,0], {marker: this.options.markerLocation});//see below
this.setLayer( this._layer );
map.on({
// 'layeradd': this._onLayerAddRemove,
// 'layerremove': this._onLayerAddRemove
'resize':this._handleAutoresize()
}, this);
return this._container;
},
onRemove: function(map) {
this._recordsCache = {};
// map.off({
// 'layeradd': this._onLayerAddRemove,
// 'layerremove': this._onLayerAddRemove
// }, this);
},
// _onLayerAddRemove: function(e) {
// //console.info('_onLayerAddRemove');
// //without this, run setLayer also for each Markers!! to optimize!
// if(e.layer instanceof L.LayerGroup)
// if( L.stamp(e.layer) != L.stamp(this._layer) )
// this.setLayer(e.layer);
// },
setLayer: function(layer) { //set search layer at runtime
//this.options.layer = layer; //setting this, run only this._recordsFromLayer()
this._layer = layer;
this._layer.addTo(this._map);
if(this._markerLoc)
this._layer.addLayer(this._markerLoc);
return this;
},
showAlert: function(text) {
text = text || this.options.textErr;
this._alert.style.display = 'block';
this._alert.innerHTML = text;
clearTimeout(this.timerAlert);
var that = this;
this.timerAlert = setTimeout(function() {
that.hideAlert();
},this.options.autoCollapseTime);
return this;
},
hideAlert: function() {
this._alert.style.display = 'none';
return this;
},
cancel: function() {
this._input.value = '';
this._handleKeypress({keyCode:8});//simulate backspace keypress
this._input.size = this._inputMinSize;
this._input.focus();
this._cancel.style.display = 'none';
return this;
},
expand: function() {
this._input.style.display = 'block';
L.DomUtil.addClass(this._container, 'search-exp');
this._input.focus();
this._map.on('dragstart', this.collapse, this);
return this;
},
collapse: function() {
this._hideTooltip();
this.cancel();
this._alert.style.display = 'none';
this._input.style.display = 'none';
this._input.blur();
this._cancel.style.display = 'none';
L.DomUtil.removeClass(this._container, 'search-exp');
//this._markerLoc.hide();//maybe unuseful
this._map.off('dragstart', this.collapse, this);
this.fire('search_collapsed');
return this;
},
collapseDelayed: function() { //collapse after delay, used on_input blur
if (!this.options.autoCollapse) return this;
var that = this;
clearTimeout(this.timerCollapse);
this.timerCollapse = setTimeout(function() {
that.collapse();
}, this.options.autoCollapseTime);
return this;
},
collapseDelayedStop: function() {
clearTimeout(this.timerCollapse);
return this;
},
////start DOM creations
_createAlert: function(className) {
var alert = L.DomUtil.create('div', className, this._container);
alert.style.display = 'none';
L.DomEvent
.on(alert, 'click', L.DomEvent.stop, this)
.on(alert, 'click', this.hideAlert, this);
return alert;
},
_createInput: function (text, className) {
var input = L.DomUtil.create('input', className, this._container);
input.type = 'text';
input.size = this._inputMinSize;
input.value = '';
input.autocomplete = 'off';
input.placeholder = text;
input.style.display = 'none';
L.DomEvent
.disableClickPropagation(input)
.on(input, 'keyup', this._handleKeypress, this)
.on(input, 'keydown', this._handleAutoresize, this)
.on(input, 'blur', this.collapseDelayed, this)
.on(input, 'focus', this.collapseDelayedStop, this);
return input;
},
_createCancel: function (title, className) {
var cancel = L.DomUtil.create('a', className, this._container);
cancel.href = '#';
cancel.title = title;
cancel.style.display = 'none';
cancel.innerHTML = "<span>&otimes;</span>";//imageless(see css)
L.DomEvent
.on(cancel, 'click', L.DomEvent.stop, this)
.on(cancel, 'click', this.cancel, this);
return cancel;
},
_createButton: function (title, className) {
var button = L.DomUtil.create('a', className, this._container);
button.href = '#';
button.title = title;
L.DomEvent
.on(button, 'click', L.DomEvent.stop, this)
.on(button, 'click', this._handleSubmit, this)
.on(button, 'focus', this.collapseDelayedStop, this)
.on(button, 'blur', this.collapseDelayed, this);
return button;
},
_createTooltip: function(className) {
var tool = L.DomUtil.create('div', className, this._container);
tool.style.display = 'none';
var that = this;
L.DomEvent
.disableClickPropagation(tool)
.on(tool, 'blur', this.collapseDelayed, this)
.on(tool, 'mousewheel', function(e) {
that.collapseDelayedStop();
L.DomEvent.stopPropagation(e);//disable zoom map
}, this)
.on(tool, 'mouseover', function(e) {
that.collapseDelayedStop();
}, this);
return tool;
},
_createTip: function(text, val) {//val is object in recordCache, usually is Latlng
var tip;
if(this.options.callTip)
{
tip = this.options.callTip(text,val); //custom tip node or html string
if(typeof tip === 'string')
{
var tmpNode = L.DomUtil.create('div');
tmpNode.innerHTML = tip;
tip = tmpNode.firstChild;
}
}
else
{
tip = L.DomUtil.create('a', '');
tip.href = '#';
tip.innerHTML = text;
}
L.DomUtil.addClass(tip, 'search-tip');
tip._text = text; //value replaced in this._input and used by _autoType
L.DomEvent
.disableClickPropagation(tip)
.on(tip, 'click', L.DomEvent.stop, this)
.on(tip, 'click', function(e) {
this._input.value = text;
this._handleAutoresize();
this._input.focus();
this._hideTooltip();
if(this.options.tipAutoSubmit)//go to location at once
this._handleSubmit();
}, this);
return tip;
},
//////end DOM creations
_filterRecords: function(text) { //Filter this._recordsCache case insensitive and much more..
var regFilter = new RegExp("^[.]$|[\[\]|()*]",'g'), //remove . * | ( ) ] [
I, regSearch,
frecords = {};
text = text.replace(regFilter,''); //sanitize text
I = this.options.initial ? '^' : ''; //search only initial text
//TODO add option for case sesitive search, also showLocation
regSearch = new RegExp(I + text,'i');
//TODO use .filter or .map
for(var key in this._recordsCache)
if( regSearch.test(key) )
frecords[key]= this._recordsCache[key];
return frecords;
},
showTooltip: function() {
var filteredRecords, newTip;
this._countertips = 0;
//FIXME problem with jsonp/ajax when remote filter has different behavior of this._filterRecords
if(this.options.layer)
filteredRecords = this._filterRecords( this._input.value );
else
filteredRecords = this._recordsCache;
this._tooltip.innerHTML = '';
this._tooltip.currentSelection = -1; //inizialized for _handleArrowSelect()
for(var key in filteredRecords)//fill tooltip
{
if(++this._countertips == this.options.tooltipLimit) break;
newTip = this._createTip(key, filteredRecords[key] );
this._tooltip.appendChild(newTip);
}
if(this._countertips > 0)
{
this._tooltip.style.display = 'block';
if(this._autoTypeTmp)
this._autoType();
this._autoTypeTmp = this.options.autoType;//reset default value
}
else
this._hideTooltip();
this._tooltip.scrollTop = 0;
return this._countertips;
},
_hideTooltip: function() {
this._tooltip.style.display = 'none';
this._tooltip.innerHTML = '';
return 0;
},
_defaultFilterJSON: function(json) { //default callback for filter data
var jsonret = {},
propName = this.options.propertyName,
propLoc = this.options.propertyLoc;
if( L.Util.isArray(propLoc) )
for(var i in json)
jsonret[ json[i][propName] ]= L.latLng( json[i][ propLoc[0] ], json[i][ propLoc[1] ] );
else
for(var n in json)
jsonret[ json[n][propName] ]= L.latLng( json[n][ propLoc ] );
//TODO verify json[n].hasOwnProperty(propName)
//throw new Error("propertyName '"+propName+"' not found in JSON data");
return jsonret;
},
_recordsFromJsonp: function(text, callAfter) { //extract searched records from remote jsonp service
//TODO remove script node after call run
var that = this;
L.Control.Search.callJsonp = function(data) { //jsonp callback
var fdata = that._filterJSON(data);//_filterJSON defined in inizialize...
callAfter(fdata);
}
var script = L.DomUtil.create('script','search-jsonp', document.getElementsByTagName('body')[0] ),
url = L.Util.template(this.options.url+'&'+this.options.jsonpParam+'=L.Control.Search.callJsonp', {s: text}); //parsing url
//rnd = '&_='+Math.floor(Math.random()*10000);
//TODO add rnd param or randomize callback name! in recordsFromJsonp
script.type = 'text/javascript';
script.src = url;
return this;
//may be return {abort: function() { script.parentNode.removeChild(script); } };
},
_recordsFromAjax: function(text, callAfter) { //Ajax request
if (window.XMLHttpRequest === undefined) {
window.XMLHttpRequest = function() {
try { return new ActiveXObject("Microsoft.XMLHTTP.6.0"); }
catch (e1) {
try { return new ActiveXObject("Microsoft.XMLHTTP.3.0"); }
catch (e2) { throw new Error("XMLHttpRequest is not supported"); }
}
};
}
var request = new XMLHttpRequest(),
url = L.Util.template(this.options.url, {s: text}), //parsing url
//rnd = '&_='+Math.floor(Math.random()*10000);
//TODO add rnd param or randomize callback name! in recordsFromAjax
response = {};
request.open("GET", url);
var that = this;
request.onreadystatechange = function() {
if(request.readyState === 4 && request.status === 200) {
response = JSON.parse(request.responseText);
var fdata = that._filterJSON(response);//_filterJSON defined in inizialize...
callAfter(fdata);
}
};
request.send();
return this;
},
_recordsFromLayer: function() { //return table: key,value from layer
var retRecords = {},
propName = this.options.propertyName,
loc;
this._layer.eachLayer(function(layer) {
if(layer instanceof SearchMarker) return;
if(layer instanceof L.Marker)
{
if(layer.options.hasOwnProperty(propName))
{
loc = layer.getLatLng();
loc.layer = layer;
retRecords[ layer.options[propName] ] = loc;
}else if(layer.feature.properties.hasOwnProperty(propName)){
loc = layer.getLatLng();
loc.layer = layer;
retRecords[ layer.feature.properties[propName] ] = loc;
}else{
console.log("propertyName '"+propName+"' not found in marker", layer);
}
}
else if(layer.hasOwnProperty('feature'))//GeoJSON layer
{
if(layer.feature.properties.hasOwnProperty(propName))
{
loc = layer.getBounds().getCenter();
loc.layer = layer;
retRecords[ layer.feature.properties[propName] ] = loc;
}
else
console.log("propertyName '"+propName+"' not found in feature", layer);
}
},this);
return retRecords;
},
_autoType: function() {
//TODO implements autype without selection(useful for mobile device)
var start = this._input.value.length,
firstRecord = this._tooltip.firstChild._text,
end = firstRecord.length;
if (firstRecord.indexOf(this._input.value) === 0) { // If prefix match
this._input.value = firstRecord;
this._handleAutoresize();
if (this._input.createTextRange) {
var selRange = this._input.createTextRange();
selRange.collapse(true);
selRange.moveStart('character', start);
selRange.moveEnd('character', end);
selRange.select();
}
else if(this._input.setSelectionRange) {
this._input.setSelectionRange(start, end);
}
else if(this._input.selectionStart) {
this._input.selectionStart = start;
this._input.selectionEnd = end;
}
}
},
_hideAutoType: function() { // deselect text:
var sel;
if ((sel = this._input.selection) && sel.empty) {
sel.empty();
}
else if (this._input.createTextRange) {
sel = this._input.createTextRange();
sel.collapse(true);
var end = this._input.value.length;
sel.moveStart('character', end);
sel.moveEnd('character', end);
sel.select();
}
else {
if (this._input.getSelection) {
this._input.getSelection().removeAllRanges();
}
this._input.selectionStart = this._input.selectionEnd;
}
},
_handleKeypress: function (e) { //run _input keyup event
switch(e.keyCode)
{
case 27: //Esc
this.collapse();
break;
case 13: //Enter
if(this._countertips == 1)
this._handleArrowSelect(1);
this._handleSubmit(); //do search
break;
case 38://Up
this._handleArrowSelect(-1);
break;
case 40://Down
this._handleArrowSelect(1);
break;
case 37://Left
case 39://Right
case 16://Shift
case 17://Ctrl
//case 32://Space
break;
case 8://backspace
case 46://delete
this._autoTypeTmp = false;//disable temporarily autoType
break;
default://All keys
if(this._input.value.length)
this._cancel.style.display = 'block';
else
this._cancel.style.display = 'none';
if(this._input.value.length >= this.options.minLength)
{
var that = this;
clearTimeout(this.timerKeypress); //cancel last search request while type in
this.timerKeypress = setTimeout(function() { //delay before request, for limit jsonp/ajax request
that._fillRecordsCache();
}, this.options.delayType);
}
else
this._hideTooltip();
}
},
_fillRecordsCache: function() {
//TODO important optimization!!! always append data in this._recordsCache
// now _recordsCache content is emptied and replaced with new data founded
// always appending data on _recordsCache give the possibility of caching ajax, jsonp and layersearch!
//
//TODO here insert function that search inputText FIRST in _recordsCache keys and if not find results..
// run one of callbacks search(callData,jsonpUrl or options.layer) and run this.showTooltip
//
//TODO change structure of _recordsCache
// like this: _recordsCache = {"text-key1": {loc:[lat,lng], ..other attributes.. }, {"text-key2": {loc:[lat,lng]}...}, ...}
// in this mode every record can have a free structure of attributes, only 'loc' is required
var inputText = this._input.value,
that;
L.DomUtil.addClass(this._container, 'search-load');
if(this.options.callData) //CUSTOM SEARCH CALLBACK(USUALLY FOR AJAX SEARCHING)
{
that = this;
this.options.callData(inputText, function(jsonraw) {
that._recordsCache = that._filterJSON(jsonraw);
that.showTooltip();
L.DomUtil.removeClass(that._container, 'search-load');
});
}
else if(this.options.url) //JSONP/AJAX REQUEST
{
if(this.options.jsonpParam)
{
that = this;
this._recordsFromJsonp(inputText, function(data) {// is async request then it need callback
that._recordsCache = data;
that.showTooltip();
L.DomUtil.removeClass(that._container, 'search-load');
});
}
else
{
that = this;
this._recordsFromAjax(inputText, function(data) {// is async request then it need callback
that._recordsCache = data;
that.showTooltip();
L.DomUtil.removeClass(that._container, 'search-load');
});
}
}
else if(this.options.layer) //SEARCH ELEMENTS IN PRELOADED LAYER
{
this._recordsCache = this._recordsFromLayer(); //fill table key,value from markers into layer
this.showTooltip();
L.DomUtil.removeClass(this._container, 'search-load');
}
},
_handleAutoresize: function() { //autoresize this._input
//TODO refact _handleAutoresize now is not accurate
if (this._input.style.maxWidth != this._map._container.offsetWidth) //If maxWidth isn't the same as when first set, reset to current Map width
this._input.style.maxWidth = L.DomUtil.getStyle(this._map._container, 'width');
if(this.options.autoResize && (this._container.offsetWidth + 45 < this._map._container.offsetWidth))
this._input.size = this._input.value.length<this._inputMinSize ? this._inputMinSize : this._input.value.length;
},
_handleArrowSelect: function(velocity) {
var searchTips = this._tooltip.hasChildNodes() ? this._tooltip.childNodes : [];
for (i=0; i<searchTips.length; i++)
L.DomUtil.removeClass(searchTips[i], 'search-tip-select');
if ((velocity == 1 ) && (this._tooltip.currentSelection >= (searchTips.length - 1))) {// If at end of list.
L.DomUtil.addClass(searchTips[this._tooltip.currentSelection], 'search-tip-select');
}
else if ((velocity == -1 ) && (this._tooltip.currentSelection <= 0)) { // Going back up to the search box.
this._tooltip.currentSelection = -1;
}
else if (this._tooltip.style.display != 'none') { // regular up/down
this._tooltip.currentSelection += velocity;
L.DomUtil.addClass(searchTips[this._tooltip.currentSelection], 'search-tip-select');
this._input.value = searchTips[this._tooltip.currentSelection]._text;
// scroll:
var tipOffsetTop = searchTips[this._tooltip.currentSelection].offsetTop;
if (tipOffsetTop + searchTips[this._tooltip.currentSelection].clientHeight >= this._tooltip.scrollTop + this._tooltip.clientHeight) {
this._tooltip.scrollTop = tipOffsetTop - this._tooltip.clientHeight + searchTips[this._tooltip.currentSelection].clientHeight;
}
else if (tipOffsetTop <= this._tooltip.scrollTop) {
this._tooltip.scrollTop = tipOffsetTop;
}
}
},
_handleSubmit: function() { //button and tooltip click and enter submit
this._hideAutoType();
this.hideAlert();
this._hideTooltip();
if(this._input.style.display == 'none') //on first click show _input only
this.expand();
else
{
if(this._input.value === '') //hide _input only
this.collapse();
else
{
var loc = this._getLocation(this._input.value);
if(loc===false)
this.showAlert();
else
{
this.showLocation(loc, this._input.value);
this.fire('search_locationfound', {
latlng: loc,
text: this._input.value,
layer: loc.layer ? loc.layer : null
});
}
//this.collapse();
//FIXME if collapse in _handleSubmit hide _markerLoc!
}
}
},
_getLocation: function(key) { //extract latlng from _recordsCache
if( this._recordsCache.hasOwnProperty(key) )
return this._recordsCache[key];//then after use .loc attribute
else
return false;
},
showLocation: function(latlng, title) { //set location on map from _recordsCache
if(this.options.zoom)
this._map.setView(latlng, this.options.zoom);
else
this._map.panTo(latlng);
if(this._markerLoc)
{
this._markerLoc.setLatLng(latlng); //show circle/marker in location found
this._markerLoc.setTitle(title);
this._markerLoc.show();
if(this.options.animateLocation)
this._markerLoc.animate();
//TODO showLocation: start animation after setView or panTo, maybe with map.on('moveend')...
}
//FIXME autoCollapse option hide this._markerLoc before that visualized!!
if(this.options.autoCollapse)
this.collapse();
return this;
}
});
var SearchMarker = L.Marker.extend({
includes: L.Mixin.Events,
options: {
radius: 10,
weight: 3,
color: '#e03',
stroke: true,
fill: false,
title: '',
//TODO add custom icon!
marker: false //show icon optional, show only circleLoc
},
initialize: function (latlng, options) {
L.setOptions(this, options);
L.Marker.prototype.initialize.call(this, latlng, options);
this._circleLoc = new L.CircleMarker(latlng, this.options);
//TODO add inner circle
},
onAdd: function (map) {
L.Marker.prototype.onAdd.call(this, map);
map.addLayer(this._circleLoc);
this.hide();
},
onRemove: function (map) {
L.Marker.prototype.onRemove.call(this, map);
map.removeLayer(this._circleLoc);
},
setLatLng: function (latlng) {
L.Marker.prototype.setLatLng.call(this, latlng);
this._circleLoc.setLatLng(latlng);
return this;
},
setTitle: function(title) {
title = title || '';
this.options.title = title;
if(this._icon)
this._icon.title = title;
return this;
},
show: function() {
if(this.options.marker)
{
if(this._icon)
this._icon.style.display = 'block';
if(this._shadow)
this._shadow.style.display = 'block';
//this._bringToFront();
}
if(this._circleLoc)
{
this._circleLoc.setStyle({fill: this.options.fill, stroke: this.options.stroke});
//this._circleLoc.bringToFront();
}
return this;
},
hide: function() {
if(this._icon)
this._icon.style.display = 'none';
if(this._shadow)
this._shadow.style.display = 'none';
if(this._circleLoc)
this._circleLoc.setStyle({fill: false, stroke: false});
return this;
},
animate: function() {
//TODO refact animate() more smooth! like this: http://goo.gl/DDlRs
var circle = this._circleLoc,
tInt = 200, //time interval
ss = 10, //frames
mr = parseInt(circle._radius/ss),
oldrad = this.options.radius,
newrad = circle._radius * 2.5,
acc = 0;
circle._timerAnimLoc = setInterval(function() {
acc += 0.5;
mr += acc; //adding acceleration
newrad -= mr;
circle.setRadius(newrad);
if(newrad<oldrad)
{
clearInterval(circle._timerAnimLoc);
circle.setRadius(oldrad);//reset radius
//if(typeof afterAnimCall == 'function')
//afterAnimCall();
//TODO use create event 'animateEnd' in SearchMarker
}
}, tInt);
return this;
}
});
L.Map.addInitHook(function () {
if (this.options.searchControl) {
this.searchControl = L.control.search(this.options.searchControl);
this.addControl(this.searchControl);
}
});
L.control.search = function (options) {
return new L.Control.Search(options);
};
}).call(this);

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View file

@ -0,0 +1,102 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Leaflet.Control.Search</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="stylesheet" href="style.css" />
</head>
<body id="home">
<h2>Leaflet.Control.Search</h2>
<div id="desc">
A Leaflet Control for search markers/features location by attribute<br />
and much more.
<div style="position:absolute;top:0;right:-120px">
<iframe src="http://ghbtns.com/github-btn.html?user=stefanocudini&amp;repo=leaflet-search&amp;type=watch&amp;count=true" allowtransparency="true" frameborder="0" scrolling="0" width="104px" height="20px"></iframe>
</div>
<br />
Other useful stuff for <a href="http://labs.easyblog.it/maps/">Web Mapping...</a>
</div>
<div style="clear:both"></div>
<div class="contents">
<h4>Features</h4>
<ul id="ff">
<li>Autocomplete</li>
<li>No require external Ajax libs</li>
<li>Retrieve data locations by Ajax/Jsonp</li>
<li>Pre-filtering data from Ajax/Jsonp</li>
<li>Complete fields remapping for remote Jsonp service</li>
<li>Data source callback support</li>
<li>Localization placeholder and text alert</li>
<li>Autozoom on location founded</li>
<li>Autoresize textbox</li>
<li>Customize tooltip menu</li>
<li>Many options to customize the behavior</li>
<li>Support search in features collection</li>
</ul>
</div>
<div class="contents">
<h4>Examples</h4>
<ul id="examples">
<li><a href="examples/simple.html">Simple</a></li>
<li><a href="examples/geojson-layer.html">GeoJSON features</a></li>
<li><a href="examples/ajax.html">Ajax</a></li>
<li><a href="examples/jsonp.html">Jsonp</a></li>
<li><a href="examples/ajax-jquery.html">Ajax by jQuery</a></li>
<li><a href="examples/jsonp-filtered.html">Jsonp Filtered</a></li>
<li><a href="examples/ajax-bulk.html">Bulk data</a></li>
<li><a href="examples/custom-tip.html">Custom Tip</a></li>
<li><a href="examples/google-geocoding.html">GeoCode Search - Google Geocoding API</a></li>
<li><a href="examples/nominatim.html">GeoCode Search - OSM Nominatim API</a></li>
<li><a href="examples/cloudmade.html">GeoCode Search - Cloudmade API</a></li>
<li><a href="examples/mobile.html">Mobile styled</a></li>
<li><a href="examples/twitter.html">Twitter API</a></li>
</ul>
</div>
<div class="contents">
<h4>Code repositories</h4>
<a target="_blank" href="https://github.com/stefanocudini/leaflet-search">Github.com</a>
<br />
<a target="_blank" href="https://bitbucket.org/zakis_/leaflet-search">Bitbucket.org</a>
<br />
<a target="_blank" href="https://npmjs.org/package/leaflet-search">Node Packaged Module</a>
<br />
<a target="_blank" href="https://atmosphere.meteor.com/package/leaflet-search">Atmosphere Meteor JS</a>
<br />
<h4>Website</h4>
<a href="http://labs.easyblog.it/maps/leaflet-search/">labs.easyblog.it/maps/leaflet-search</a>
<br />
<h4>Download</h4>
<ul>
<li><a href="https://github.com/stefanocudini/leaflet-search/archive/master.zip">Dev Pack (.zip)</a></li>
<li><a href="dist/leaflet-search.src.js">Source Code (.js)</a></li>
<li><a href="dist/leaflet-search.min.js">Compressed (.min.js)</a></li>
</ul>
</div>
<div id="copy"><a href="http://labs.easyblog.it/">Labs</a> &bull; <a rel="author" href="http://labs.easyblog.it/stefano-cudini/">Stefano Cudini</a></div>
<a href="https://github.com/stefanocudini/leaflet-search"><img id="ribbon" src="https://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub"></a>
<div style="clear:both;font-size:.85em;margin-bottom:1em">
<b>For questions and bugs</b> I recommend you to <a href="https://github.com/stefanocudini/leaflet-search/issues">create New Issue</a> on Github repository.</strong><br />
Or to obtain a fast response consult <a href="https://groups.google.com/forum/?hl=it&fromgroups=#!forum/leaflet-js">Official Leaflet community forum</a>.<br />
<br />
This is a micro discussion area for methods of implementation.<br />
</div>
<div id="comments">
<div id="disqus_thread"></div>
</div>
<script>var disqus_shortname = 'easyblog-it'</script>
<script type="text/javascript" src="/labs-common.js"></script>
</body>
</html>

View file

@ -0,0 +1,11 @@
Package.describe({
summary: "Leaflet Control Search"
});
Package.on_use(function (api, where) {
api.add_files('dist/leaflet-search.min.js', 'client');
api.add_files('dist/leaflet-search.min.css', 'client');
api.add_files('images/search-icon.png', 'client');
api.add_files('images/loader.gif', 'client');
//TODO server-side searching...
});

View file

@ -0,0 +1,34 @@
{
"name": "leaflet-search",
"version": "1.4.7",
"description": "Leaflet Control for searching markers/features by attribute on map or remote searching in jsonp/ajax",
"repository": {
"type": "git",
"url": "git@github.com:stefanocudini/leaflet-search.git"
},
"homepage": "http://labs.easyblog.it/maps/leaflet-search/",
"author": {
"name": "Stefano Cudini",
"email": "stefano.cudini@gmail.com",
"url": "http://labs.easyblog.it/"
},
"license": "MIT",
"keywords": [
"gis",
"map",
"leaflet"
],
"dependencies": {
"leaflet": "*"
},
"devDependencies": {
"grunt": "~0.4.2",
"grunt-contrib-uglify": "~0.2.7",
"grunt-contrib-concat": "~0.3.0",
"grunt-contrib-clean": "~0.5.0",
"grunt-contrib-cssmin": "~0.7.0",
"grunt-contrib-jshint": "~0.7.2",
"grunt-contrib-watch": "~0.5.3",
"grunt-todos": "~0.2.0"
}
}

View file

@ -0,0 +1,8 @@
{
"name": "leaflet-search",
"description": "Leaflet Control for searching markers/features by attribute on map or remote searching in jsonp/ajax",
"homepage": "http://labs.easyblog.it/maps/leaflet-search/",
"author": "Stefano Cudini <stefano.cudini@gmail.com>",
"version": "1.4.7",
"git": "https://github.com/stefanocudini/leaflet-search.git"
}

View file

@ -0,0 +1,121 @@
.leaflet-container .leaflet-control-search {
position:relative;
float:left;
background:#fff;
color:#1978cf;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
border-radius: 4px;
background-color: rgba(255, 255, 255, 0.8);
z-index:1000;
box-shadow: 0 1px 7px rgba(0,0,0,0.65);
margin-left: 10px;
margin-top: 10px;
}
.leaflet-control-search.search-exp {/*expanded*/
box-shadow: 0 1px 7px #999;
background: #fff;
}
.leaflet-control-search .search-input {
display:block;
float:left;
background: #fff;
border:1px solid #666;
border-radius:2px;
height:18px;
padding:0 18px 0 2px;
margin:3px 0 3px 3px;
}
.leaflet-control-search.search-load .search-input {
background: url('../images/loader.gif') no-repeat center right #fff;
}
.leaflet-control-search.search-load .search-cancel {
visibility:hidden;
}
.leaflet-control-search .search-cancel {
display:block;
width:22px;
height:18px;
position:absolute;
right:22px;
margin:3px 0;
background: url('../images/search-icon.png') no-repeat 0 -46px;
text-decoration:none;
filter: alpha(opacity=80);
opacity: 0.8;
}
.leaflet-control-search .search-cancel:hover {
filter: alpha(opacity=100);
opacity: 1;
}
.leaflet-control-search .search-cancel span {
display:none;/* comment for cancel button imageless */
font-size:18px;
line-height:20px;
color:#ccc;
font-weight:bold;
}
.leaflet-control-search .search-cancel:hover span {
color:#aaa;
}
.leaflet-control-search .search-button {
display:block;
float:left;
width:26px;
height:26px;
background: url('../images/search-icon.png') no-repeat 2px 2px;
border-radius:4px;
}
.leaflet-control-search .search-button:hover {
background: url('../images/search-icon.png') no-repeat 2px -22px;
}
.leaflet-control-search .search-tooltip {
position:absolute;
top:100%;
left:0;
float:left;
min-width:80px;
max-height:106px;/*(.search-tip height * 5)*/
box-shadow: 0 0 8px rgba(0,0,0,0.4);
-webkit-border-radius: 5px;
-webkit-border-top-left-radius: 0;
-moz-border-radius: 5px;
-moz-border-radius-topleft: 0;
border-radius: 5px;
border-top-left-radius: 0;
background-color: rgba(0, 0, 0, 0.25);
z-index:1010;
overflow-y:auto;
overflow-x:hidden;
}
.leaflet-control-search .search-tip {
font-size:.85em;
margin:2px;
padding:2px;
display:block;
color:black;
background: #ddd;
border-radius:.25em;
text-decoration:none;
white-space:nowrap;
font-size:.85em;
vertical-align:center;
}
.leaflet-control-search .search-tip-select,
.leaflet-control-search .search-tip:hover,
.leaflet-control-search .search-button:hover {
background-color: #fff;
}
.leaflet-control-search .search-alert {
cursor:pointer;
clear:both;
font-size:.75em;
margin-bottom:5px;
padding:0 .25em;
color:#e00;
font-weight:bold;
border-radius:.25em;
}

View file

@ -0,0 +1,839 @@
(function() {
L.Control.Search = L.Control.extend({
includes: L.Mixin.Events,
//
// Name Data passed Description
//
//Managed Events:
// search_locationfound {latlng, title} fired after moved and show markerLocation
// search_collapsed {} fired after control was collapsed
//
//Public methods:
// setLayer() L.LayerGroup() set layer search at runtime
// showAlert() 'Text message' Show alert message
//
options: {
url: '', //url for search by ajax request, ex: "search.php?q={s}"
jsonpParam: null, //jsonp param name for search by jsonp service, ex: "callback"
layer: null, //layer where search markers(is a L.LayerGroup)
callData: null, //function that fill _recordsCache, passed searching text by first param and callback in second
//TODO important! implements uniq option 'sourceData' that recognizes source type: url,array,callback or layer
//TODO implement can do research on multiple sources
propertyName: 'title', //property in marker.options(or feature.properties for vector layer) trough filter elements in layer
propertyLoc: 'loc', //field name for remapping location, using array: ['latname','lonname'] for select double fields(ex. ['lat','lon'] )
//TODO implement sub property filter for propertyName,propertyLoc like this: "prop.subprop.title"
callTip: null, //function that return row tip html node(or html string), receive text tooltip in first param
filterJSON: null, //callback for filtering data to _recordsCache
minLength: 1, //minimal text length for autocomplete
initial: true, //search elements only by initial text
autoType: true, //complete input with first suggested result and select this filled-in text.
delayType: 400, //delay while typing for show tooltip
tooltipLimit: -1, //limit max results to show in tooltip. -1 for no limit.
tipAutoSubmit: true, //auto map panTo when click on tooltip
autoResize: true, //autoresize on input change
autoCollapse: false, //collapse search control after submit(on button or on tips if enabled tipAutoSubmit)
//TODO add option for persist markerLoc after collapse!
autoCollapseTime: 1200, //delay for autoclosing alert and collapse after blur
animateLocation: true, //animate a circle over location found
circleLocation: true, //draw a circle in location found
markerLocation: false, //draw a marker in location found
zoom: null, //zoom after pan to location found, default: map.getZoom()
text: 'Search...', //placeholder value
textCancel: 'Cancel', //title in cancel button
textErr: 'Location not found', //error message
position: 'topleft'
//TODO add option collapsed, like control.layers
},
//FIXME option condition problem {autoCollapse: true, markerLocation: true} not show location
//FIXME option condition problem {autoCollapse: false }
initialize: function(options) {
L.Util.setOptions(this, options || {});
this._inputMinSize = this.options.text ? this.options.text.length : 10;
this._layer = this.options.layer || new L.LayerGroup();
this._filterJSON = this.options.filterJSON || this._defaultFilterJSON;
this._autoTypeTmp = this.options.autoType; //useful for disable autoType temporarily in delete/backspace keydown
this._countertips = 0; //number of tips items
this._recordsCache = {}; //key,value table! that store locations! format: key,latlng
},
onAdd: function (map) {
this._map = map;
this._container = L.DomUtil.create('div', 'leaflet-control-search');
this._input = this._createInput(this.options.text, 'search-input');
this._tooltip = this._createTooltip('search-tooltip');
this._cancel = this._createCancel(this.options.textCancel, 'search-cancel');
this._button = this._createButton(this.options.text, 'search-button');
this._alert = this._createAlert('search-alert');
if(this.options.circleLocation || this.options.markerLocation)
this._markerLoc = new SearchMarker([0,0], {marker: this.options.markerLocation});//see below
this.setLayer( this._layer );
map.on({
// 'layeradd': this._onLayerAddRemove,
// 'layerremove': this._onLayerAddRemove
'resize':this._handleAutoresize()
}, this);
return this._container;
},
onRemove: function(map) {
this._recordsCache = {};
// map.off({
// 'layeradd': this._onLayerAddRemove,
// 'layerremove': this._onLayerAddRemove
// }, this);
},
// _onLayerAddRemove: function(e) {
// //console.info('_onLayerAddRemove');
// //without this, run setLayer also for each Markers!! to optimize!
// if(e.layer instanceof L.LayerGroup)
// if( L.stamp(e.layer) != L.stamp(this._layer) )
// this.setLayer(e.layer);
// },
setLayer: function(layer) { //set search layer at runtime
//this.options.layer = layer; //setting this, run only this._recordsFromLayer()
this._layer = layer;
this._layer.addTo(this._map);
if(this._markerLoc)
this._layer.addLayer(this._markerLoc);
return this;
},
showAlert: function(text) {
text = text || this.options.textErr;
this._alert.style.display = 'block';
this._alert.innerHTML = text;
clearTimeout(this.timerAlert);
var that = this;
this.timerAlert = setTimeout(function() {
that.hideAlert();
},this.options.autoCollapseTime);
return this;
},
hideAlert: function() {
this._alert.style.display = 'none';
return this;
},
cancel: function() {
this._input.value = '';
this._handleKeypress({keyCode:8});//simulate backspace keypress
this._input.size = this._inputMinSize;
this._input.focus();
this._cancel.style.display = 'none';
return this;
},
expand: function() {
this._input.style.display = 'block';
L.DomUtil.addClass(this._container, 'search-exp');
this._input.focus();
this._map.on('dragstart', this.collapse, this);
return this;
},
collapse: function() {
this._hideTooltip();
this.cancel();
this._alert.style.display = 'none';
this._input.style.display = 'none';
this._input.blur();
this._cancel.style.display = 'none';
L.DomUtil.removeClass(this._container, 'search-exp');
//this._markerLoc.hide();//maybe unuseful
this._map.off('dragstart', this.collapse, this);
this.fire('search_collapsed');
return this;
},
collapseDelayed: function() { //collapse after delay, used on_input blur
if (!this.options.autoCollapse) return this;
var that = this;
clearTimeout(this.timerCollapse);
this.timerCollapse = setTimeout(function() {
that.collapse();
}, this.options.autoCollapseTime);
return this;
},
collapseDelayedStop: function() {
clearTimeout(this.timerCollapse);
return this;
},
////start DOM creations
_createAlert: function(className) {
var alert = L.DomUtil.create('div', className, this._container);
alert.style.display = 'none';
L.DomEvent
.on(alert, 'click', L.DomEvent.stop, this)
.on(alert, 'click', this.hideAlert, this);
return alert;
},
_createInput: function (text, className) {
var input = L.DomUtil.create('input', className, this._container);
input.type = 'text';
input.size = this._inputMinSize;
input.value = '';
input.autocomplete = 'off';
input.placeholder = text;
input.style.display = 'none';
L.DomEvent
.disableClickPropagation(input)
.on(input, 'keyup', this._handleKeypress, this)
.on(input, 'keydown', this._handleAutoresize, this)
.on(input, 'blur', this.collapseDelayed, this)
.on(input, 'focus', this.collapseDelayedStop, this);
return input;
},
_createCancel: function (title, className) {
var cancel = L.DomUtil.create('a', className, this._container);
cancel.href = '#';
cancel.title = title;
cancel.style.display = 'none';
cancel.innerHTML = "<span>&otimes;</span>";//imageless(see css)
L.DomEvent
.on(cancel, 'click', L.DomEvent.stop, this)
.on(cancel, 'click', this.cancel, this);
return cancel;
},
_createButton: function (title, className) {
var button = L.DomUtil.create('a', className, this._container);
button.href = '#';
button.title = title;
L.DomEvent
.on(button, 'click', L.DomEvent.stop, this)
.on(button, 'click', this._handleSubmit, this)
.on(button, 'focus', this.collapseDelayedStop, this)
.on(button, 'blur', this.collapseDelayed, this);
return button;
},
_createTooltip: function(className) {
var tool = L.DomUtil.create('div', className, this._container);
tool.style.display = 'none';
var that = this;
L.DomEvent
.disableClickPropagation(tool)
.on(tool, 'blur', this.collapseDelayed, this)
.on(tool, 'mousewheel', function(e) {
that.collapseDelayedStop();
L.DomEvent.stopPropagation(e);//disable zoom map
}, this)
.on(tool, 'mouseover', function(e) {
that.collapseDelayedStop();
}, this);
return tool;
},
_createTip: function(text, val) {//val is object in recordCache, usually is Latlng
var tip;
if(this.options.callTip)
{
tip = this.options.callTip(text,val); //custom tip node or html string
if(typeof tip === 'string')
{
var tmpNode = L.DomUtil.create('div');
tmpNode.innerHTML = tip;
tip = tmpNode.firstChild;
}
}
else
{
tip = L.DomUtil.create('a', '');
tip.href = '#';
tip.innerHTML = text;
}
L.DomUtil.addClass(tip, 'search-tip');
tip._text = text; //value replaced in this._input and used by _autoType
L.DomEvent
.disableClickPropagation(tip)
.on(tip, 'click', L.DomEvent.stop, this)
.on(tip, 'click', function(e) {
this._input.value = text;
this._handleAutoresize();
this._input.focus();
this._hideTooltip();
if(this.options.tipAutoSubmit)//go to location at once
this._handleSubmit();
}, this);
return tip;
},
//////end DOM creations
_filterRecords: function(text) { //Filter this._recordsCache case insensitive and much more..
var regFilter = new RegExp("^[.]$|[\[\]|()*]",'g'), //remove . * | ( ) ] [
I, regSearch,
frecords = {};
text = text.replace(regFilter,''); //sanitize text
I = this.options.initial ? '^' : ''; //search only initial text
//TODO add option for case sesitive search, also showLocation
regSearch = new RegExp(I + text,'i');
//TODO use .filter or .map
for(var key in this._recordsCache)
if( regSearch.test(key) )
frecords[key]= this._recordsCache[key];
return frecords;
},
showTooltip: function() {
var filteredRecords, newTip;
this._countertips = 0;
//FIXME problem with jsonp/ajax when remote filter has different behavior of this._filterRecords
if(this.options.layer)
filteredRecords = this._filterRecords( this._input.value );
else
filteredRecords = this._recordsCache;
this._tooltip.innerHTML = '';
this._tooltip.currentSelection = -1; //inizialized for _handleArrowSelect()
for(var key in filteredRecords)//fill tooltip
{
if(++this._countertips == this.options.tooltipLimit) break;
newTip = this._createTip(key, filteredRecords[key] );
this._tooltip.appendChild(newTip);
}
if(this._countertips > 0)
{
this._tooltip.style.display = 'block';
if(this._autoTypeTmp)
this._autoType();
this._autoTypeTmp = this.options.autoType;//reset default value
}
else
this._hideTooltip();
this._tooltip.scrollTop = 0;
return this._countertips;
},
_hideTooltip: function() {
this._tooltip.style.display = 'none';
this._tooltip.innerHTML = '';
return 0;
},
_defaultFilterJSON: function(json) { //default callback for filter data
var jsonret = {},
propName = this.options.propertyName,
propLoc = this.options.propertyLoc;
if( L.Util.isArray(propLoc) )
for(var i in json)
jsonret[ json[i][propName] ]= L.latLng( json[i][ propLoc[0] ], json[i][ propLoc[1] ] );
else
for(var n in json)
jsonret[ json[n][propName] ]= L.latLng( json[n][ propLoc ] );
//TODO verify json[n].hasOwnProperty(propName)
//throw new Error("propertyName '"+propName+"' not found in JSON data");
return jsonret;
},
_recordsFromJsonp: function(text, callAfter) { //extract searched records from remote jsonp service
//TODO remove script node after call run
var that = this;
L.Control.Search.callJsonp = function(data) { //jsonp callback
var fdata = that._filterJSON(data);//_filterJSON defined in inizialize...
callAfter(fdata);
}
var script = L.DomUtil.create('script','search-jsonp', document.getElementsByTagName('body')[0] ),
url = L.Util.template(this.options.url+'&'+this.options.jsonpParam+'=L.Control.Search.callJsonp', {s: text}); //parsing url
//rnd = '&_='+Math.floor(Math.random()*10000);
//TODO add rnd param or randomize callback name! in recordsFromJsonp
script.type = 'text/javascript';
script.src = url;
return this;
//may be return {abort: function() { script.parentNode.removeChild(script); } };
},
_recordsFromAjax: function(text, callAfter) { //Ajax request
if (window.XMLHttpRequest === undefined) {
window.XMLHttpRequest = function() {
try { return new ActiveXObject("Microsoft.XMLHTTP.6.0"); }
catch (e1) {
try { return new ActiveXObject("Microsoft.XMLHTTP.3.0"); }
catch (e2) { throw new Error("XMLHttpRequest is not supported"); }
}
};
}
var request = new XMLHttpRequest(),
url = L.Util.template(this.options.url, {s: text}), //parsing url
//rnd = '&_='+Math.floor(Math.random()*10000);
//TODO add rnd param or randomize callback name! in recordsFromAjax
response = {};
request.open("GET", url);
var that = this;
request.onreadystatechange = function() {
if(request.readyState === 4 && request.status === 200) {
response = JSON.parse(request.responseText);
var fdata = that._filterJSON(response);//_filterJSON defined in inizialize...
callAfter(fdata);
}
};
request.send();
return this;
},
_recordsFromLayer: function() { //return table: key,value from layer
var retRecords = {},
propName = this.options.propertyName,
loc;
this._layer.eachLayer(function(layer) {
if(layer instanceof SearchMarker) return;
if(layer instanceof L.Marker)
{
if(layer.options.hasOwnProperty(propName))
{
loc = layer.getLatLng();
loc.layer = layer;
retRecords[ layer.options[propName] ] = loc;
}else if(layer.feature.properties.hasOwnProperty(propName)){
loc = layer.getLatLng();
loc.layer = layer;
retRecords[ layer.feature.properties[propName] ] = loc;
}else{
console.log("propertyName '"+propName+"' not found in marker", layer);
}
}
else if(layer.hasOwnProperty('feature'))//GeoJSON layer
{
if(layer.feature.properties.hasOwnProperty(propName))
{
loc = layer.getBounds().getCenter();
loc.layer = layer;
retRecords[ layer.feature.properties[propName] ] = loc;
}
else
console.log("propertyName '"+propName+"' not found in feature", layer);
}
},this);
return retRecords;
},
_autoType: function() {
//TODO implements autype without selection(useful for mobile device)
var start = this._input.value.length,
firstRecord = this._tooltip.firstChild._text,
end = firstRecord.length;
if (firstRecord.indexOf(this._input.value) === 0) { // If prefix match
this._input.value = firstRecord;
this._handleAutoresize();
if (this._input.createTextRange) {
var selRange = this._input.createTextRange();
selRange.collapse(true);
selRange.moveStart('character', start);
selRange.moveEnd('character', end);
selRange.select();
}
else if(this._input.setSelectionRange) {
this._input.setSelectionRange(start, end);
}
else if(this._input.selectionStart) {
this._input.selectionStart = start;
this._input.selectionEnd = end;
}
}
},
_hideAutoType: function() { // deselect text:
var sel;
if ((sel = this._input.selection) && sel.empty) {
sel.empty();
}
else if (this._input.createTextRange) {
sel = this._input.createTextRange();
sel.collapse(true);
var end = this._input.value.length;
sel.moveStart('character', end);
sel.moveEnd('character', end);
sel.select();
}
else {
if (this._input.getSelection) {
this._input.getSelection().removeAllRanges();
}
this._input.selectionStart = this._input.selectionEnd;
}
},
_handleKeypress: function (e) { //run _input keyup event
switch(e.keyCode)
{
case 27: //Esc
this.collapse();
break;
case 13: //Enter
if(this._countertips == 1)
this._handleArrowSelect(1);
this._handleSubmit(); //do search
break;
case 38://Up
this._handleArrowSelect(-1);
break;
case 40://Down
this._handleArrowSelect(1);
break;
case 37://Left
case 39://Right
case 16://Shift
case 17://Ctrl
//case 32://Space
break;
case 8://backspace
case 46://delete
this._autoTypeTmp = false;//disable temporarily autoType
break;
default://All keys
if(this._input.value.length)
this._cancel.style.display = 'block';
else
this._cancel.style.display = 'none';
if(this._input.value.length >= this.options.minLength)
{
var that = this;
clearTimeout(this.timerKeypress); //cancel last search request while type in
this.timerKeypress = setTimeout(function() { //delay before request, for limit jsonp/ajax request
that._fillRecordsCache();
}, this.options.delayType);
}
else
this._hideTooltip();
}
},
_fillRecordsCache: function() {
//TODO important optimization!!! always append data in this._recordsCache
// now _recordsCache content is emptied and replaced with new data founded
// always appending data on _recordsCache give the possibility of caching ajax, jsonp and layersearch!
//
//TODO here insert function that search inputText FIRST in _recordsCache keys and if not find results..
// run one of callbacks search(callData,jsonpUrl or options.layer) and run this.showTooltip
//
//TODO change structure of _recordsCache
// like this: _recordsCache = {"text-key1": {loc:[lat,lng], ..other attributes.. }, {"text-key2": {loc:[lat,lng]}...}, ...}
// in this mode every record can have a free structure of attributes, only 'loc' is required
var inputText = this._input.value,
that;
L.DomUtil.addClass(this._container, 'search-load');
if(this.options.callData) //CUSTOM SEARCH CALLBACK(USUALLY FOR AJAX SEARCHING)
{
that = this;
this.options.callData(inputText, function(jsonraw) {
that._recordsCache = that._filterJSON(jsonraw);
that.showTooltip();
L.DomUtil.removeClass(that._container, 'search-load');
});
}
else if(this.options.url) //JSONP/AJAX REQUEST
{
if(this.options.jsonpParam)
{
that = this;
this._recordsFromJsonp(inputText, function(data) {// is async request then it need callback
that._recordsCache = data;
that.showTooltip();
L.DomUtil.removeClass(that._container, 'search-load');
});
}
else
{
that = this;
this._recordsFromAjax(inputText, function(data) {// is async request then it need callback
that._recordsCache = data;
that.showTooltip();
L.DomUtil.removeClass(that._container, 'search-load');
});
}
}
else if(this.options.layer) //SEARCH ELEMENTS IN PRELOADED LAYER
{
this._recordsCache = this._recordsFromLayer(); //fill table key,value from markers into layer
this.showTooltip();
L.DomUtil.removeClass(this._container, 'search-load');
}
},
_handleAutoresize: function() { //autoresize this._input
//TODO refact _handleAutoresize now is not accurate
if (this._input.style.maxWidth != this._map._container.offsetWidth) //If maxWidth isn't the same as when first set, reset to current Map width
this._input.style.maxWidth = L.DomUtil.getStyle(this._map._container, 'width');
if(this.options.autoResize && (this._container.offsetWidth + 45 < this._map._container.offsetWidth))
this._input.size = this._input.value.length<this._inputMinSize ? this._inputMinSize : this._input.value.length;
},
_handleArrowSelect: function(velocity) {
var searchTips = this._tooltip.hasChildNodes() ? this._tooltip.childNodes : [];
for (i=0; i<searchTips.length; i++)
L.DomUtil.removeClass(searchTips[i], 'search-tip-select');
if ((velocity == 1 ) && (this._tooltip.currentSelection >= (searchTips.length - 1))) {// If at end of list.
L.DomUtil.addClass(searchTips[this._tooltip.currentSelection], 'search-tip-select');
}
else if ((velocity == -1 ) && (this._tooltip.currentSelection <= 0)) { // Going back up to the search box.
this._tooltip.currentSelection = -1;
}
else if (this._tooltip.style.display != 'none') { // regular up/down
this._tooltip.currentSelection += velocity;
L.DomUtil.addClass(searchTips[this._tooltip.currentSelection], 'search-tip-select');
this._input.value = searchTips[this._tooltip.currentSelection]._text;
// scroll:
var tipOffsetTop = searchTips[this._tooltip.currentSelection].offsetTop;
if (tipOffsetTop + searchTips[this._tooltip.currentSelection].clientHeight >= this._tooltip.scrollTop + this._tooltip.clientHeight) {
this._tooltip.scrollTop = tipOffsetTop - this._tooltip.clientHeight + searchTips[this._tooltip.currentSelection].clientHeight;
}
else if (tipOffsetTop <= this._tooltip.scrollTop) {
this._tooltip.scrollTop = tipOffsetTop;
}
}
},
_handleSubmit: function() { //button and tooltip click and enter submit
this._hideAutoType();
this.hideAlert();
this._hideTooltip();
if(this._input.style.display == 'none') //on first click show _input only
this.expand();
else
{
if(this._input.value === '') //hide _input only
this.collapse();
else
{
var loc = this._getLocation(this._input.value);
if(loc===false)
this.showAlert();
else
{
this.showLocation(loc, this._input.value);
this.fire('search_locationfound', {
latlng: loc,
text: this._input.value,
layer: loc.layer ? loc.layer : null
});
}
//this.collapse();
//FIXME if collapse in _handleSubmit hide _markerLoc!
}
}
},
_getLocation: function(key) { //extract latlng from _recordsCache
if( this._recordsCache.hasOwnProperty(key) )
return this._recordsCache[key];//then after use .loc attribute
else
return false;
},
showLocation: function(latlng, title) { //set location on map from _recordsCache
if(this.options.zoom)
this._map.setView(latlng, this.options.zoom);
else
this._map.panTo(latlng);
if(this._markerLoc)
{
this._markerLoc.setLatLng(latlng); //show circle/marker in location found
this._markerLoc.setTitle(title);
this._markerLoc.show();
if(this.options.animateLocation)
this._markerLoc.animate();
//TODO showLocation: start animation after setView or panTo, maybe with map.on('moveend')...
}
//FIXME autoCollapse option hide this._markerLoc before that visualized!!
if(this.options.autoCollapse)
this.collapse();
return this;
}
});
var SearchMarker = L.Marker.extend({
includes: L.Mixin.Events,
options: {
radius: 10,
weight: 3,
color: '#e03',
stroke: true,
fill: false,
title: '',
//TODO add custom icon!
marker: false //show icon optional, show only circleLoc
},
initialize: function (latlng, options) {
L.setOptions(this, options);
L.Marker.prototype.initialize.call(this, latlng, options);
this._circleLoc = new L.CircleMarker(latlng, this.options);
//TODO add inner circle
},
onAdd: function (map) {
L.Marker.prototype.onAdd.call(this, map);
map.addLayer(this._circleLoc);
this.hide();
},
onRemove: function (map) {
L.Marker.prototype.onRemove.call(this, map);
map.removeLayer(this._circleLoc);
},
setLatLng: function (latlng) {
L.Marker.prototype.setLatLng.call(this, latlng);
this._circleLoc.setLatLng(latlng);
return this;
},
setTitle: function(title) {
title = title || '';
this.options.title = title;
if(this._icon)
this._icon.title = title;
return this;
},
show: function() {
if(this.options.marker)
{
if(this._icon)
this._icon.style.display = 'block';
if(this._shadow)
this._shadow.style.display = 'block';
//this._bringToFront();
}
if(this._circleLoc)
{
this._circleLoc.setStyle({fill: this.options.fill, stroke: this.options.stroke});
//this._circleLoc.bringToFront();
}
return this;
},
hide: function() {
if(this._icon)
this._icon.style.display = 'none';
if(this._shadow)
this._shadow.style.display = 'none';
if(this._circleLoc)
this._circleLoc.setStyle({fill: false, stroke: false});
return this;
},
animate: function() {
//TODO refact animate() more smooth! like this: http://goo.gl/DDlRs
var circle = this._circleLoc,
tInt = 200, //time interval
ss = 10, //frames
mr = parseInt(circle._radius/ss),
oldrad = this.options.radius,
newrad = circle._radius * 2.5,
acc = 0;
circle._timerAnimLoc = setInterval(function() {
acc += 0.5;
mr += acc; //adding acceleration
newrad -= mr;
circle.setRadius(newrad);
if(newrad<oldrad)
{
clearInterval(circle._timerAnimLoc);
circle.setRadius(oldrad);//reset radius
//if(typeof afterAnimCall == 'function')
//afterAnimCall();
//TODO use create event 'animateEnd' in SearchMarker
}
}, tInt);
return this;
}
});
L.Map.addInitHook(function () {
if (this.options.searchControl) {
this.searchControl = L.control.search(this.options.searchControl);
this.addControl(this.searchControl);
}
});
L.control.search = function (options) {
return new L.Control.Search(options);
};
}).call(this);

View file

@ -0,0 +1,67 @@
/* SEARCH */
.leaflet-control.leaflet-control-search {
z-index:2000;
}
.leaflet-control-search .search-input {
display:block;
float:left;
background: #fff;
border:1px solid #666;
border-radius:2px;
height:24px;
font-size:1.25em;
padding:0 .125em;
margin:3px;
padding-right:30px;
}
.leaflet-control-search .search-button:hover,
.leaflet-control-search .search-button {
background-image: url('../images/search-icon-mobile.png');
-webkit-border-radius: 4px;
border-radius: 4px;
background-position: 1px 1px;
width:32px;
height:32px;
}
.leaflet-control-search.search-load .search-input {
background: url('../images/loader.gif') no-repeat center right #fff;
}
.leaflet-control-search .search-cancel {
background-image: url('../images/search-icon-mobile.png');
-webkit-border-radius: 4px;
border-radius: 4px;
background-position: 0px -62px;
width:26px;
height:26px;
right:34px;
margin:3px;
}
.leaflet-control-search .search-tooltip {
max-height:142px;/*(.search-tip height * 5)*/
}
.leaflet-control-search .search-tip {
font-size:1em;
margin:2px;
padding:2px;
display:block;
color:black;
background: rgba(255,255,255,0.8);
border-radius:.25em;
text-decoration:none;
white-space:nowrap;
vertical-align:center;
}
.leaflet-control-search .search-tip .climbo-icon-mini {
float:right;
display:block;
white-space:nowrap;
}
.leaflet-control-search .search-button:hover,
.leaflet-control-search .search-tip-select,
.leaflet-control-search .search-tip:hover {
background-color: #fff;
}
.leaflet-control-search .search-alert {
font-size:1.2em;
}

View file

@ -0,0 +1,97 @@
body {
background:#b5d0d0;
color:#285585;
font-family:Arial;
}
body#home {
background:url('images/back.png') no-repeat top left #b5d0d0;
margin-left:150px;
}
a {
color:#1978cf;
}
a:hover {
color:#fff;
}
h2, h3, h4 {
white-space:nowrap;
margin:1em 0 0 0;
}
h3 a,
h3 a:hover {
text-decoration:none;
}
#desc {
float: left;
margin-bottom: 1em;
position: relative;
white-space:nowrap;
font-size:1em;
}
#map {
border-radius:.125em;
border:2px solid #1978cf;
box-shadow: 0 0 8px #999;
float:left;
width:600px;
height:400px;
}
ul {
font-size:.85em;
margin:0;
padding:0;
}
li {
margin:0 0 2px 18px;
}
#post-it {
width:9em;
height:9em;
margin-left:2em;
padding:1em;
float:left;
background:#fbf5bf;
border:1px solid #c6bb58;
box-shadow: 2px 2px 6px #999;
color:#666;
}
#copy {
position:fixed;
z-index:1000;
right:150px;
top:-8px;
font-size:.85em;
padding:8px 8px 2px 8px;
background: #323b44;
border: 2px solid #737c85;
border-radius:.7em;
opacity: 0.9;
box-shadow:0 0 8px #5f7182;
color:#eee
}
#copy a {
color:#ccc;
text-decoration:none
}
#copy a:hover {
color:#fff
}
#ribbon {
position: absolute;
top: 0;
right: 0;
border: 0;
filter: alpha(opacity=80);
-khtml-opacity: .8;
-moz-opacity: .8;
opacity: .8;
}
.contents {
float:left;
margin:0 2em 2em 0;
}
#comments {
clear:both;
}

View file

@ -0,0 +1,22 @@
{
"name": "normalize-css",
"version": "2.1.3",
"main": "normalize.css",
"author": "Nicolas Gallagher",
"ignore": [
"CHANGELOG.md",
"CONTRIBUTING.md",
"component.json",
"test.html"
],
"homepage": "https://github.com/necolas/normalize.css",
"_release": "2.1.3",
"_resolution": {
"type": "version",
"tag": "v2.1.3",
"commit": "be14934fad255a01ed345de6ae59ff612adcf3b9"
},
"_source": "git://github.com/necolas/normalize.css.git",
"_target": "*",
"_originalSource": "normalize-css"
}

View file

@ -0,0 +1,19 @@
Copyright (c) Nicolas Gallagher and Jonathan Neal
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,54 @@
# normalize.css v2
Normalize.css is a customisable CSS file that makes browsers render all
elements more consistently and in line with modern standards.
The project relies on researching the differences between default browser
styles in order to precisely target only the styles that need or benefit from
normalizing.
[Check out the demo](http://necolas.github.io/normalize.css/latest/test.html)
## Install
Download from the [project page](http://necolas.github.io/normalize.css/).
Install with [Bower](http://bower.io/): `bower install --save normalize-css`
Install with [Component(1)](http://component.io/): `component install necolas/normalize.css`
## What does it do?
* Preserves useful defaults, unlike many CSS resets.
* Normalizes styles for a wide range of elements.
* Corrects bugs and common browser inconsistencies.
* Improves usability with subtle improvements.
* Explains what code does using detailed comments.
## How to use it
No other styles should come before Normalize.css.
It is recommended that you include the `normalize.css` file as untouched
library code.
## Browser support
* Google Chrome
* Mozilla Firefox 4+
* Apple Safari 5+
* Opera 12+
* Internet Explorer 8+
[Normalize.css v1 provides legacy browser
support](https://github.com/necolas/normalize.css/tree/v1) (IE 6+, Safari 4+),
but is no longer actively developed.
## Contributing
Please read the CONTRIBUTING.md
## Acknowledgements
Normalize.css is a project by [Nicolas Gallagher](https://github.com/necolas),
co-created with [Jonathan Neal](https://github.com/jonathantneal).

View file

@ -0,0 +1,12 @@
{
"name": "normalize-css",
"version": "2.1.3",
"main": "normalize.css",
"author": "Nicolas Gallagher",
"ignore": [
"CHANGELOG.md",
"CONTRIBUTING.md",
"component.json",
"test.html"
]
}

Some files were not shown because too many files have changed in this diff Show more