size
+@*/
+
+Ox.LeafletMapMarker = function(options) {
+
+ options = Ox.extend({
+ map: null,
+ place: null
+ }, options);
+
+ var that = this,
+ areaSize = {
+ 100: 10, // 10 x 10 m
+ 10000: 12, // 100 x 100 m
+ 1000000: 14, // 1 x 1 km
+ 100000000: 16, // 10 x 10 km
+ 10000000000: 18, // 100 x 100 km
+ 1000000000000: 20, // 1,000 x 1,000 km
+ 100000000000000: 22 // 10,000 x 10,000 km
+ },
+ themeData = Ox.Theme.getThemeData(),
+ typeColor = {};
+
+ [
+ 'country', 'region', 'city', 'borough',
+ 'street', 'building', 'feature'
+ ].forEach(function(type) {
+ typeColor[type] = themeData[
+ 'mapPlace' + Ox.toTitleCase(type) + 'Color'
+ ];
+ });
+
+ Ox.forEach(options, function(val, key) {
+ that[key] = val;
+ });
+ console.log('place', options.place);
+ that.marker = new L.Marker([options.place.center.lat, options.place.center.lng], {
+ // raiseOnDrag: false,
+ // shape: {coords: [8, 8, 8], type: 'circle'}
+ //title: that.place.name,
+ //zIndex: 1000
+ }).addTo(options.map.map);
+
+ // setOptions();
+
+ function click() {
+ var key = that.map.getKey(),
+ place, bounds, southWest, northEast;
+ if (!that.place.selected) {
+ if (
+ that.map.options('editable')
+ && (key == 'meta' || key == 'shift')
+ ) {
+ place = that.map.getSelectedPlace();
+ }
+ if (place) {
+ bounds = new L.LatLngBounds(
+ new L.LatLng(place.south, place.west),
+ new L.LatLng(place.north, place.east)
+ );
+ // bounds = bounds.union(that.place.bounds);
+ southWest = bounds.getSouthWest();
+ northEast = bounds.getNorthEast();
+ that.map.newPlace(new Ox.LeafletMapPlace({
+ // fixme: duplicated, see Ox.Map.js
+ alternativeNames: [],
+ countryCode: '',
+ editable: true,
+ geoname: '',
+ id: '_' + Ox.encodeBase32(Ox.uid()), // fixme: stupid
+ map: that.map,
+ name: '',
+ type: 'feature',
+ south: southWest.lat,
+ west: southWest.lng,
+ north: northEast.lat,
+ east: northEast.lng
+ }));
+ } else {
+ console.log('place', that.place);
+ that.map.options({selected: that.place.id});
+ }
+ } else {
+ if (key == 'meta') {
+ that.map.options({selected: null});
+ } else {
+ that.map.panToPlace();
+ }
+ }
+ }
+
+ function dblclick() {
+ that.place.selected && that.map.zoomToPlace();
+ }
+
+ function dragstart(e) {
+ Ox.$body.addClass('OxDragging');
+ }
+
+ function drag(e) {
+ var northSouth = (that.place.north - that.place.south) / 2,
+ lat = Ox.limit(
+ e.latLng.lat(),
+ Ox.MIN_LATITUDE + northSouth,
+ Ox.MAX_LATITUDE - northSouth
+ ),
+ lng = e.latLng.lng(),
+ span = Math.min(
+ that.place.sizeEastWest * Ox.getDegreesPerMeter(lat) / 2, 179.99999999
+ ),
+ degreesPerMeter = Ox.getDegreesPerMeter(lat);
+ that.place.south += lat - that.place.lat;
+ that.place.north += lat - that.place.lat;
+ that.place.west = lng - span;
+ that.place.east = lng + span;
+ if (that.place.west < -180) {
+ that.place.west += 360;
+ } else if (that.place.east > 180) {
+ that.place.east -= 360;
+ }
+ Ox.Log('Map', 'west', that.place.west, 'east', that.place.east, 'span', span);
+ that.place.update();
+ that.marker.setOptions({
+ position: that.place.center
+ });
+ that.place.rectangle.update();
+ }
+
+ function dragend(e) {
+ Ox.$body.removeClass('OxDragging');
+ that.map.triggerEvent('changeplaceend', that.place);
+ }
+
+ function getMarkerImage(options, callback) {
+ // fixme: unused
+ options = Ox.extend({
+ background: [255, 0, 0],
+ editing: false,
+ result: false,
+ selected: false,
+ size: 16
+ }, options);
+ var background = options.result ? [255, 255, 0] : [255, 0, 0],
+ border = options.editing ? [128, 128, 255] :
+ options.selected ? [255, 255, 255] : [0, 0, 0],
+ c = Ox.canvas(options.size, options.size),
+ image,
+ r = options.size / 2;
+ if (Ox.isArray(background)) {
+ c.context.fillStyle = 'rgba(' + background.join(', ') + ', 0.5)';
+ c.context.arc(r, r, r - 2, 0, 360);
+ c.context.fill();
+ renderImage();
+ } else {
+ image = new Image();
+ image.onload = renderImage;
+ image.src = background;
+ }
+ function renderImage() {
+ //var i;
+ if (Ox.isString(background)) {
+ c.context.drawImage(image, 1, 1, options.size - 2, options.size - 2);
+ /*
+ c.imageData = c.context.getImageData(0, 0, options.size, options.size);
+ c.data = c.imageData.data;
+ for (i = 3; i < c.data.length; i += 1) {
+ c.data[i] = Math.round(c.data[i] * 0.5);
+ }
+ c.context.putImageData(c.imageData, 0, 0);
+ */
+ }
+ c.context.beginPath();
+ c.context.lineWidth = 2;
+ c.context.strokeStyle = 'rgb(' + border.join(', ') + ')';
+ c.context.arc(r, r, r - 1, 0, 360);
+ c.context.stroke();
+ callback(new google.maps.MarkerImage(
+ c.canvas.toDataURL(),
+ new google.maps.Size(options.size, options.size),
+ new google.maps.Point(0, 0),
+ new google.maps.Point(r, r)
+ ));
+ }
+ }
+
+ function mouseover(e) {
+ var offset = that.map.offset(),
+ xy = that.map.overlayView.getProjection()
+ .fromLatLngToContainerPixel(e.latLng);
+ that.tooltip.show(
+ offset.left + Math.round(xy.x) - 4,
+ offset.top + Math.round(xy.y) + 20
+ );
+ }
+
+ function mouseout() {
+ that.tooltip.hide();
+ }
+
+ function setOptions() {
+ // workaround to prevent marker from appearing twice
+ // after setting draggable from true to false (google maps bug)
+ // var fix = that.marker.getDraggable() && !that.place.editing,
+ // color = that.map.options('markerColor'),
+ // size = that.map.options('markerSize');
+ var fix = false,
+ color = that.map.options('markerColor'),
+ size = that.map.options('markerSize');
+ //Ox.Log('Map', 'setOptions, that.map: ', that.map)
+ if (color == 'auto') {
+ that.color = typeColor[that.place.type] || typeColor['mapPlaceFeatureColor'];
+ } else if (Ox.isArray(color)) {
+ that.color = color;
+ } else {
+ that.color = color(that.place);
+ }
+ if (size == 'auto') {
+ that.size = 8;
+ Ox.forEach(areaSize, function(size, area) {
+ if (that.place.area >= area) {
+ that.size = size;
+ } else {
+ return false; // break
+ }
+ });
+ } else if (Ox.isNumber(size)) {
+ that.size = size;
+ } else {
+ that.size = size(that.place);
+ }
+ // that.marker.setOptions({
+ // // fixme: cursor remains pointer
+ // cursor: that.place.editing ? 'move' : 'pointer',
+ // draggable: that.place.editing,
+ // // icon: Ox.MapMarkerImage({
+ // // color: that.color,
+ // // mode: that.place.editing ? 'editing' :
+ // // that.place.selected ? 'selected' : 'normal',
+ // // size: that.size,
+ // // type: that.place.id[0] == '_' ? 'result' : 'place'
+ // // }),
+ // position: that.place.center
+ // });
+ if (fix) {
+ that.marker.setVisible(false);
+ setTimeout(function() {
+ that.marker.setVisible(true);
+ }, 0);
+ }
+ setTooltip();
+ }
+
+ function setTooltip() {
+ that.tooltip && that.tooltip.remove();
+ that.tooltip = Ox.Tooltip({
+ title: ''
+ + ''
+ + that.map.options('markerTooltip')(that.place) + '
'
+ })
+ .addClass('OxMapMarkerTooltip');
+ }
+
+ /*@
+ add add to map
+ () -> add to map, returns MapMarker
+ @*/
+ that.add = function() {
+ that.marker.addTo(that.map.map);
+ that.marker.on('click', click);
+ that.marker.on('dblclick', dblclick);
+ // that.marker.on('mouseover', mouseover);
+ // that.marker.on('mouseout', mouseout);
+ // google.maps.event.addListener(that.marker, 'click', click);
+ // google.maps.event.addListener(that.marker, 'dblclick', dblclick);
+ // google.maps.event.addListener(that.marker, 'mouseover', mouseover);
+ // google.maps.event.addListener(that.marker, 'mouseout', mouseout);
+ return that;
+ };
+
+ /*@
+ edit edit marker
+ () -> edit marker, returns MapMarker
+ @*/
+ that.edit = function() {
+ setOptions();
+ google.maps.event.addListener(that.marker, 'dragstart', dragstart);
+ google.maps.event.addListener(that.marker, 'drag', drag);
+ google.maps.event.addListener(that.marker, 'dragend', dragend);
+ return that;
+ };
+
+ /*@
+ remove remove marker
+ () -> remove marker from map, returns MapMarker
+ @*/
+ that.remove = function() {
+ that.marker.setMap(null);
+ google.maps.event.clearListeners(that.marker);
+ return that;
+ };
+
+ /*@
+ submit submit marker
+ () -> clear edit listeners, returns MapMarker
+ @*/
+ that.submit = function() {
+ google.maps.event.clearListeners(that.marker, 'dragstart');
+ google.maps.event.clearListeners(that.marker, 'drag');
+ google.maps.event.clearListeners(that.marker, 'dragend');
+ return that;
+ }
+
+ /*@
+ update update marker
+ () -> update marker, returns MapMarker
+ @*/
+ that.update = function() {
+ setOptions();
+ return that;
+ }
+
+ return that;
+
+};
diff --git a/source/UI/js/Map/LeafletMapPlace.js b/source/UI/js/Map/LeafletMapPlace.js
new file mode 100644
index 00000000..e7cdd7bc
--- /dev/null
+++ b/source/UI/js/Map/LeafletMapPlace.js
@@ -0,0 +1,220 @@
+'use strict';
+
+/*@
+Ox.MapPlace MapPlace Object
+ (options) -> MapPlace Object
+ options Options object
+ east
+ editing
+ geoname
+ map
+ markerColor 0> 0]>
+ markerSize
+ name
+ north
+ selected
+ south
+ type
+ visible
+ west
+@*/
+
+Ox.LeafletMapPlace = function(options) {
+
+ options = Ox.extend({
+ east: 0,
+ editing: false,
+ geoname: '',
+ map: null,
+ name: '',
+ north: 0,
+ selected: false,
+ south: 0,
+ type: '',
+ visible: false,
+ west: 0
+ }, options);
+
+ var that = this;
+
+ Ox.forEach(options, function(val, key) {
+ that[key] = val;
+ });
+
+ update();
+
+ function update(updateMarker) {
+ // console.log('foo', that.north, that.east, that.south, that.west);
+ that.points = {
+ ne: new L.LatLng(that.north, that.east),
+ sw: new L.LatLng(that.south, that.west)
+ };
+ console.log('points', JSON.stringify(that.points, null, 2));
+ that.bounds = new L.latLngBounds(that.points.sw, that.points.ne);
+ that.center = that.bounds.getCenter();
+ that.lat = that.center.lat;
+ that.lng = that.center.lng;
+ Ox.extend(that.points, {
+ e: new L.LatLng(that.lat, that.east),
+ s: new L.LatLng(that.south, that.lng),
+ se: new L.LatLng(that.south, that.east),
+ n: new L.LatLng(that.north, that.lng),
+ nw: new L.LatLng(that.north, that.west),
+ w: new L.LatLng(that.lat, that.west)
+ });
+ // fixme: use bounds.toSpan()
+ that.sizeNorthSouth = (that.north - that.south)
+ * Ox.EARTH_CIRCUMFERENCE / 360;
+ that.sizeEastWest = (that.east + (that.west > that.east ? 360 : 0) - that.west)
+ * Ox.getMetersPerDegree(that.lat);
+ that.area = Ox.getArea(
+ {lat: that.south, lng: that.west},
+ {lat: that.north, lng: that.east}
+ );
+ if (!that.marker) {
+ that.marker = new Ox.LeafletMapMarker({
+ map: that.map,
+ place: that
+ });
+ that.rectangle = new Ox.LeafletMapRectangle({
+ map: that.map,
+ place: that
+ });
+ } else if (updateMarker) {
+ that.marker.update();
+ that.rectangle.update();
+ }
+ }
+
+ function editable() {
+ return that.map.options('editable') && that.editable;
+ }
+
+ /*@
+ add add
+ @*/
+ that.add = function() {
+ that.visible = true;
+ that.marker.add();
+ return that;
+ };
+
+ /*@
+ cancel cancel
+ @*/
+ that.cancel = function() {
+ if (editable()) {
+ that.undo();
+ that.editing = false;
+ that.marker.update();
+ that.rectangle.deselect();
+ }
+ return that;
+ };
+
+ /*@
+ crossesDateline crossesDateline
+ @*/
+ that.crossesDateline = function() {
+ return that.west > that.east;
+ }
+
+ /*@
+ deselect dselect
+ @*/
+ that.deselect = function() {
+ that.editing && that.submit();
+ that.selected = false;
+ that.marker.update();
+ that.rectangle.remove();
+ return that;
+ };
+
+ /*@
+ edit edit
+ @*/
+ that.edit = function() {
+ if (editable()) {
+ that.editing = true;
+ that.original = {
+ south: that.south,
+ west: that.west,
+ north: that.north,
+ east: that.east
+ };
+ that.marker.edit();
+ that.rectangle.select();
+ }
+ return that;
+ };
+
+ // fixme: make this an Ox.Element to get options handling for free?
+ that.options = function(options) {
+ options = Ox.makeObject(arguments);
+ Ox.forEach(options, function(value, key) {
+ that[key] = value;
+ });
+ update(true);
+ };
+
+ /*@
+ remove remove
+ @*/
+ that.remove = function() {
+ that.editing && that.submit();
+ that.selected && that.deselect();
+ that.visible = false;
+ that.marker.remove();
+ return that;
+ };
+
+ /*@
+ select select
+ @*/
+ that.select = function() {
+ that.selected = true;
+ !that.visible && that.add();
+ that.marker.update();
+ that.rectangle.add();
+ return that;
+ };
+
+ /*@
+ submit submit
+ @*/
+ that.submit = function() {
+ if (editable()) {
+ that.editing = false;
+ that.marker.update();
+ that.rectangle.deselect();
+ }
+ return that;
+ };
+
+ /*@
+ update update
+ @*/
+ that.update = function(updateMarker) {
+ update(updateMarker);
+ that.map.triggerEvent('changeplace', that);
+ return that;
+ };
+
+ /*@
+ undo undo
+ @*/
+ that.undo = function() {
+ if (editable()) {
+ Ox.forEach(that.original, function(v, k) {
+ that[k] = v;
+ });
+ that.update();
+ that.marker.update();
+ that.rectangle.update();
+ }
+ return that;
+ };
+
+ return that;
+
+};
diff --git a/source/UI/js/Map/LeafletMapRectangle.js b/source/UI/js/Map/LeafletMapRectangle.js
new file mode 100644
index 00000000..99f1a19e
--- /dev/null
+++ b/source/UI/js/Map/LeafletMapRectangle.js
@@ -0,0 +1,133 @@
+'use strict';
+
+/*@
+Ox.MapRectangle MapRectangle Object
+ (options) -> MapRectangle Object
+ options Options object
+ map map
+ place place
+@*/
+
+Ox.LeafletMapRectangle = function(options) {
+
+ options = Ox.extend({
+ map: null,
+ place: null
+ }, options);
+
+ var that = this,
+ themeData = Ox.Theme.getThemeData();
+
+ Ox.forEach(options, function(val, key) {
+ that[key] = val;
+ });
+
+ /*@
+ rectangle google.maps.Rectangle
+ @*/
+ console.log('place bounds', that.place.bounds);
+ that.rectangle = new L.rectangle(that.place.bounds);
+
+ /*@
+ markers array of markers
+ @*/
+ that.markers = Ox.map(that.place.points, function(point, position) {
+ return new Ox.LeafletMapRectangleMarker({
+ map: that.map,
+ place: that.place,
+ position: position
+ });
+ });
+
+ setOptions();
+
+ function click() {
+ if (
+ that.map.options('editable')
+ && that.place.editable
+ && !that.place.editing
+ ) {
+ that.place.edit();
+ } else if (that.map.getKey() == 'meta') {
+ that.place.submit();
+ } else if (that.map.getKey() == 'shift') {
+ that.map.zoomToPlace();
+ } else {
+ that.map.panToPlace();
+ }
+ }
+
+ function setOptions() {
+ var color = '#' + Ox.toHex(themeData[
+ that.place.editing
+ ? 'mapPlaceEditingBorder'
+ : 'mapPlaceSelectedBorder'
+ ]);
+ // that.rectangle.setOptions({
+ // bounds: that.place.bounds,
+ // fillColor: color,
+ // fillOpacity: that.place.editing ? 0.1 : 0,
+ // strokeColor: color,
+ // strokeOpacity: that.place.id[0] == '_' ? 0.5 : 1,
+ // strokeWeight: 2
+ // });
+ }
+
+ /*@
+ add add
+ @*/
+ that.add = function() {
+ that.rectangle.addTo(that.map.map);
+ that.rectangle.on('click', click);
+ // google.maps.event.addListener(that.rectangle, 'click', click);
+ return that;
+ };
+
+ /*@
+ deselect deselect
+ @*/
+ that.deselect = function() {
+ setOptions();
+ Ox.Log('Map', 'MARKERS', that.markers)
+ Ox.forEach(that.markers, function(marker) {
+ marker.remove();
+ });
+ return that;
+ };
+
+ /*@
+ remove remove
+ @*/
+ that.remove = function() {
+ that.rectangle.remove();
+ // that.rectangle.setMap(null);
+ // google.maps.event.clearListeners(that.rectangle);
+ return that;
+ }
+
+ /*@
+ select select
+ @*/
+ that.select = function() {
+ setOptions();
+ Ox.forEach(that.markers, function(marker) {
+ marker.add();
+ });
+ return that;
+ };
+
+ /*@
+ update udpate
+ @*/
+ that.update = function() {
+ Ox.Log('Map', 'UPDATE...')
+ setOptions();
+ Ox.forEach(that.markers, function(marker) {
+ marker.update();
+ });
+ return that;
+ }
+
+ return that;
+
+};
diff --git a/source/UI/js/Map/LeafletMapRectangleMarker.js b/source/UI/js/Map/LeafletMapRectangleMarker.js
new file mode 100644
index 00000000..71a0e72c
--- /dev/null
+++ b/source/UI/js/Map/LeafletMapRectangleMarker.js
@@ -0,0 +1,126 @@
+'use strict';
+
+/*@
+Ox.MapRectangleMarker MapRectangleMarker Object
+ (options) -> MapRectangleMarker Object
+ options Options object
+ map map
+ place place
+ position
+@*/
+
+Ox.LeafletMapRectangleMarker = function(options) {
+
+ options = Ox.extend({
+ map: null,
+ place: null,
+ position: ''
+ }, options);
+
+ var that = this;
+
+ Ox.forEach(options, function(val, key) {
+ that[key] = val;
+ });
+ console.log('position', that.place.points, that.position);
+ // that.markerImage = new google.maps.MarkerImage
+ that.marker = new L.Marker({
+
+ });
+ // that.marker = new google.maps.Marker({
+ // cursor: that.position + '-resize',
+ // draggable: true,
+ // icon: Ox.MapMarkerImage({
+ // mode: 'editing',
+ // rectangle: true,
+ // type: that.place.id[0] == '_' ? 'result' : 'place'
+ // }),
+ // position: that.place.points[that.position],
+ // raiseOnDrag: false
+ // });
+
+ function dragstart(e) {
+ Ox.$body.addClass('OxDragging');
+ that.drag = {
+ lat: e.latLng.lat(),
+ lng: e.latLng.lng()
+ };
+ }
+
+ function drag(e) {
+ // fixme: implement shift+drag (center stays the same)
+ Ox.Log('Map', e.pixel.x, e.pixel.y)
+ var lat = Ox.limit(e.latLng.lat(), Ox.MIN_LATITUDE, Ox.MAX_LATITUDE),
+ lng = e.latLng.lng();
+ that.drag = {
+ lat: lat,
+ lng: lng
+ };
+ if (that.position.indexOf('s') > -1) {
+ that.place.south = lat;
+ }
+ if (that.position.indexOf('n') > -1) {
+ that.place.north = lat;
+ }
+ if (that.position.indexOf('w') > -1) {
+ that.place.west = lng;
+ }
+ if (that.position.indexOf('e') > -1) {
+ that.place.east = lng;
+ }
+ //Ox.Log('Map', 'west', that.place.west, 'east', that.place.east);
+ //Ox.Log('Map', 'south', that.place.south, 'north', that.place.north);
+ that.place.update();
+ that.place.marker.update();
+ that.place.rectangle.update();
+ }
+
+ function dragend(e) {
+ var south;
+ Ox.$body.removeClass('OxDragging');
+ if (that.place.south > that.place.north) {
+ south = that.place.south;
+ that.place.south = that.place.north;
+ that.place.north = south;
+ that.place.update();
+ that.place.marker.update();
+ that.place.rectangle.update();
+ }
+ that.map.triggerEvent('changeplaceend', that.place);
+ }
+
+ /*@
+ add add
+ @*/
+ that.add = function() {
+ that.marker.setMap(that.map.map);
+ google.maps.event.addListener(that.marker, 'dragstart', dragstart);
+ google.maps.event.addListener(that.marker, 'drag', drag);
+ google.maps.event.addListener(that.marker, 'dragend', dragend);
+ };
+
+ /*@
+ remove remove
+ @*/
+ that.remove = function() {
+ that.marker.setMap(null);
+ google.maps.event.clearListeners(that.marker);
+ };
+
+ /*@
+ update update
+ @*/
+ that.update = function() {
+ that.marker.setOptions({
+ icon: Ox.MapMarkerImage({
+ mode: 'editing',
+ rectangle: true,
+ type: that.place.id[0] == '_' ? 'result' : 'place'
+ }),
+ position: that.place.points[that.position]
+ });
+ };
+
+ return that;
+
+};
diff --git a/source/UI/js/Map/Map.js b/source/UI/js/Map/Map.js
index 9f5c4392..f12e1654 100644
--- a/source/UI/js/Map/Map.js
+++ b/source/UI/js/Map/Map.js
@@ -832,7 +832,6 @@ Ox.Map = function(options, self) {
}
function initMap() {
-
getMapBounds(function(mapBounds) {
//Ox.Log('Map', 'init', mapBounds.getSouthWest(), mapBounds.getNorthEast(), mapBounds.getCenter())