// vim: et:ts=4:sw=4:sts=4:ft=javascript /*@ Ox.Map <function> Basic map object # DESCRIPTION -------------------------------------------------------------- <code>Ox.Map</code> is a wrapper around the <a href="http://code.google.com/apis/maps/documentation/javascript/">Google Maps API</a>. # USAGE -------------------------------------------------------------------- () -> <f> Map object (options) -> <f> Map object (options, self) -> <f> Map object # ARGUMENTS ---------------------------------------------------------------- options <o|{}> options clickable <b|false> If true, clicking on the map finds a place editable <b|false> If true, places are editable findPlaceholder <s|"Find"> Placeholder text for the find input element maxMarkers <n|100> Maximum number of markers to be displayed places <[o]|f|null> Array of, or function that returns, place objects countryCode <s> ISO 3166 country code east <n> Longitude of the eastern boundary in degrees editable <b|false> If true, the place is editable geoname <s> Geoname (like "Paris, Île-de-France, France") lat <n> Latitude in degrees lng <n> Longitude in degrees markerColor <s|"red"> CSS color of the place marker markerSize <n|16> size of the place marker in px name <s> Name (like "Paris") north <n> Latitude of the northern boundary in degrees south <n> Latitude of the southern boundary in degrees type <s> Type (like "city" or "country") west <n> Longitude of the western boundary in degrees selected <s|""> Id of the selected place showControls <b|false> If true, show controls showLabels <b|false> If true, show labels on the map showTypes <b|false> If true, color markers according to place type statusbar <b|false> If true, the map has a statusbar toolbar <b|false> If true, the map has a toolbar self <o|{}> Shared private variable # EVENTS ------------------------------------------------------------------- addplace <!> Fires when a place has been added place <o> Place object editplace <!> Fires when a place has been edited place <o> Place object geocode <!> Fires when a google geocode request returns latLng <o|u> Query coordinates, or undefined lat <n> latitude lng <n> longitude address <s|u> Query string, or undefined results <[o]> Google Maps geocode results address_components <[o]> Address components long_name <s> Long name short_name <s> Short name types <[s]> Types (like "country" or "political") formatted_address <s> Formatted address geometry <o> Geometry bounds <o> Bounds northEast <o> North-east corner lat <n> Latitude lng <n> Longitude southWest <o> South-west corner lat <n> Latitude lng <n> Longitude location <o> Location lat <n> Latitude lng <n> Longitude location_type <s> Location type (like "APPROXIMATE") viewport <o> Viewport northEast <o> North-east corner lat <n> Latitude lng <n> Longitude southWest <o> South-west corner lat <n> Latitude lng <n> Longitude types <[s]> Types (like "country" or "political") # EXAMPLES ----------------------------------------------------------------- <script> // simple Ox.load('UI', function() { Ox.Map().appendTo(Ox.UI.$body); }); </script> > Ox.Map() === true false > Ox.Map() === false false @*/ Ox.Map = function(options, self) { self = self || {}; var that = Ox.Element({}, self) .defaults({ // fixme: isClickable? hasZoombar? clickable: false, editable: false, findPlaceholder: 'Find', maxMarkers: 100, places: null, selected: null, showControls: false, showLabels: false, showTypes: false, statusbar: false, toolbar: false, zoombar: false }) .options(options || {}) .addClass('OxMap') .click(function(e) { !$(e.target).is('input') && that.gainFocus(); }) .bindEvent({ key_0: function() { that.panToPlace() }, key_down: function() { pan(0, 1); }, key_enter: pressEnter, key_escape: pressEscape, key_equal: function() { zoom(1); }, key_l: toggleLabels, key_left: function() { pan(-1, 0); }, key_meta: function() { self.metaKey = true; $(document).one({ keyup: function() { self.metaKey = false; } }); }, key_minus: function() { zoom(-1); }, key_right: function() { pan(1, 0); }, key_shift: function() { self.shiftKey = true; $(document).one({ keyup: function() { self.shiftKey = false; } }); }, key_shift_down: function() { pan(0, 2); }, key_shift_0: function() { that.zoomToPlace(); }, key_shift_equal: function() { zoom(2); }, key_shift_left: function() { pan(-2, 0); }, key_shift_minus: function() { zoom(-2); }, key_shift_right: function() { pan(2, 0); }, key_shift_up: function() { pan(0, -2); }, key_up: function() { pan(0, -1); }, key_z: undo }); self.isAsync = Ox.isFunction(self.options.places); self.mapHeight = getMapHeight(); self.minZoom = getMinZoom(); self.placeKeys = [ 'id', 'name', 'alternativeNames', 'geoname', 'countryCode', 'type', 'lat', 'lng', 'south', 'west', 'north', 'east', 'area', 'editable' ]; self.scaleMeters = [ 50000000, 20000000, 10000000, 5000000, 2000000, 1000000, 500000, 200000, 100000, 50000, 20000, 10000, 5000, 2000, 1000, 500, 200, 100, 50, 20, 10 ]; Ox.extend(self, { metaKey: false, resultPlace: null, shiftKey: false }); if (self.options.toolbar) { self.$toolbar = Ox.Bar({ size: 24 }) .appendTo(that); self.$select = Ox.Select({ items: [ {id: 'new Place', title: 'New Place...', keyboard: 'n'}, {}, {id: 'toggleLabels', title: 'Show Labels', keyboard: 'l', checked: self.options.showLabels}, {id: 'toggleControls', title: 'Show Controls', keyboard: 'c', checked: self.options.showControls}, ], min: 0, max: 2, title: 'Options...', width: 96 }) .css({float: 'left', margin: '4px'}) .appendTo(self.$toolbar); /* self.$labelsButton = Ox.Checkbox({ title: 'Labels', width: 64 }) .css({float: 'left', margin: '4px'}) .bindEvent({ change: toggleLabels }) .appendTo(self.$toolbar) */ self.$findInput = Ox.Input({ clear: true, placeholder: self.options.findPlaceholder, width: 192 }) .css({float: 'right', margin: '4px'}) .bindEvent({ submit: submitFind }) .appendTo(self.$toolbar) } self.$map = Ox.Element() .css({ left: 0, top: self.options.toolbar * 24 + 'px', right: 0, bottom: self.options.zoombar * 16 + self.options.statusbar * 24 + 'px' }) .appendTo(that); if (self.options.zoombar) { self.$zoombar = Ox.Bar({ size: 16 }) .css({ bottom: self.options.statusbar * 24 + 'px' }) .appendTo(that); } if (self.options.statusbar) { self.$statusbar = Ox.Bar({ size: 24 }) .css({ bottom: 0 }) .appendTo(that); self.$placeFlag = $('<img>') .addClass('OxFlag') .attr({ src: Ox.PATH + 'Ox.Geo/png/icons/16/NTHH.png' }) .css({float: 'left', margin: '4px 2px 4px 4px'}) .appendTo(self.$statusbar.$element); self.$placeNameInput = Ox.Input({ placeholder: 'Name', width: 96 }) //.css({position: 'absolute', left: 4, top: 4}) .css({float: 'left', margin: '4px 2px 4px 2px'}) .appendTo(self.$statusbar); self.$placeGeonameInput = Ox.Input({ placeholder: 'Geoname', width: 96 }) //.css({position: 'absolute', left: 104, top: 4}) .css({float: 'left', margin: '4px 2px 4px 2px'}) .appendTo(self.$statusbar); self.$placeButton = Ox.Button({ title: 'New Place', width: 96 }) .css({float: 'right', margin: '4px 4px 4px 2px'}) .bindEvent({ click: clickPlaceButton }) .appendTo(self.$statusbar); } self.$navigationButtons = { 'center': Ox.Button({ title: 'close', type: 'image' }) .addClass('OxMapButton') .css({ left: '24px', top: '24px' }), 'east': Ox.Button({ title: 'right', type: 'image' }) .addClass('OxMapButton') .css({ left: '44px', top: '24px', }), 'north': Ox.Button({ title: 'up', type: 'image' }) .addClass('OxMapButton') .css({ left: '24px', top: '4px', }), 'south': Ox.Button({ title: 'down', type: 'image' }) .addClass('OxMapButton') .css({ left: '24px', top: '44px', }), 'west': Ox.Button({ title: 'left', type: 'image' }) .addClass('OxMapButton') .css({ left: '4px', top: '24px', }) }; Ox.forEach(self.$navigationButtons, function($button) { $button.attr({ src: $button.attr('src').replace('/classic/', '/modern/') }); }); self.$scaleLabel = Ox.Label({ textAlign: 'center', title: '...' }) .addClass('OxMapLabel') .css({right: '4px', top: '4px'}); if (!self.isAsync) { self.options.places.forEach(function(place) { if (Ox.isUndefined(place.id)) { place.id = Ox.encodeBase32(Ox.uid()); } }); } if (!window.googleCallback) { window.googleCallback = function() { delete window.googleCallback; initMap(); }; $.getScript('http://maps.google.com/maps/api/js?callback=googleCallback&sensor=false'); } else { (function interval() { window.google ? initMap() : setTimeout(interval, 100); }()); } function addPlaceToMap(place) { // via find, click, shift-click, or new place button Ox.print('addPlaceToMap', place) var exists = false; if (!place) { var bounds = self.map.getBounds(), center = self.map.getCenter(), southwest = new google.maps.LatLngBounds( bounds.getSouthWest(), center ).getCenter(), northeast = new google.maps.LatLngBounds( center, bounds.getNorthEast() ).getCenter(), place = new Ox.MapPlace({ alternativeNames: [], countryCode: '', editable: true, geoname: '', id: '_' + Ox.encodeBase32(Ox.uid()), // fixme: stupid map: that, name: '', type: 'feature', south: southwest.lat(), west: southwest.lng(), north: northeast.lat(), east: northeast.lng() }); } Ox.forEach(self.places, function(p, i) { if (place.bounds.equals(p.bounds)) { place = p; exists = true; return false; } }); if (!exists) { self.resultPlace && self.resultPlace.remove(); self.resultPlace = place; place.add(); } selectPlace(place.id); } function addPlaceToPlaces(data) { var place = getSelectedPlace(), country = Ox.getCountryByGeoname(place.geoname); Ox.extend(place, data); self.options.selected = place.id; place.countryCode = country ? country.code : ''; Ox.print('addP2P', data, place); place.marker.update(); self.places.push(place); self.resultPlace = null; that.triggerEvent('addplace', place) //Ox.print('SSSS', self.options.selected) } function boundsChanged() { setScale(); self.boundsChanged = true; } function canContain(outerBounds, innerBounds) { // checks if outerBounds _can_ contain innerBounds var outerSpan = outerBounds.toSpan(), innerSpan = innerBounds.toSpan(); return outerSpan.lat() > innerSpan.lat() && outerSpan.lng() > innerSpan.lng(); } function centerChanged() { self.center = self.map.getCenter(); self.centerChanged = true; } function changeZoom(event, data) { self.map.setZoom(data.value); } function clickMap(event) { if (self.options.clickable/* && !editing()*/) { getPlaceByLatLng(event.latLng, self.map.getBounds(), function(place) { if (place) { addPlaceToMap(place); //selectPlace(place.id); } else { selectPlace(null); } }); } } function clickPlaceButton() { var place = getSelectedPlace(), title = self.$placeButton.options('title'); if (title == 'New Place') { addPlaceToMap(); } else if (title == 'Add Place') { addPlaceToPlaces(); } else if (title == 'Remove Place') { } } function constructZoomInput() { //Ox.print('constructZoomInput', self.minZoom, self.maxZoom) if (self.options.zoombar) { self.$zoomInput && self.$zoomInput.removeElement(); self.$zoomInput = Ox.Range({ arrows: true, max: self.maxZoom, min: self.minZoom, size: that.width(), thumbSize: 32, thumbValue: true, value: self.map.getZoom() }) .bindEvent({ change: changeZoom }) .appendTo(self.$zoombar); } } function editing() { var place = getSelectedPlace(); return place && place.editing; } function getElevation(point, callback) { // fixme: unused if (arguments.length == 1) { callback = point; point = self.map.getCenter(); } self.elevationService.getElevationForLocations({ locations: [point] }, function(data) { callback(data.elevation); }); } function getMapBounds(callback) { // get initial map bounds var mapBounds; if (!self.isAsync) { self.options.places.forEach(function(place, i) { var bounds = getBounds(place); mapBounds = i == 0 ? bounds : mapBounds.union(bounds); }); callback(mapBounds); } else { self.options.places({}, function(result) { callback(getBounds(result.data.area)); }); } function getBounds(place) { return new google.maps.LatLngBounds( new google.maps.LatLng(place.south, place.west), new google.maps.LatLng(place.north, place.east) ); } } function getMapHeight() { return self.options.height - self.options.statusbar * 24 - self.options.toolbar * 24 - self.options.zoombar * 16; } function getMapType() { return self.options.showLabels ? 'HYBRID' : 'SATELLITE' } function getMaxZoom(point, callback) { if (arguments.length == 1) { callback = point; point = self.map.getCenter(); } Ox.print('CALLING ZOOM SERVICE', point.lat(), point.lng()) self.maxZoomService.getMaxZoomAtLatLng(point, function(data) { Ox.print('ZOOM SERVICE', data.status, data.zoom) callback(data.status == 'OK' ? data.zoom : null); }); } function getMetersPerPixel() { var mapWidth = self.$map.width(), span = self.map.getBounds().toSpan().lng(); if (span >= 360) { span = 360 * mapWidth / Ox.MAP_TILE_SIZE; } return span * Ox.getMetersPerDegree(self.map.getCenter().lat()) / mapWidth; } function getMinZoom() { return 0; return Math.ceil( Ox.log(self.mapHeight / Ox.MAP_TILE_SIZE, 2) ); } function getPlaceById(id) { var place = Ox.getObjectById(self.places, id); if (!place && self.resultPlace && self.resultPlace.id == id) { place = self.resultPlace; } //Ox.print('getPlaceById', id, place) return place; } function getPlaceByLatLng(latlng, bounds, callback) { // gets the largest place at latlng that would fit in bounds //Ox.print('ll b', latlng, bounds) var callback = arguments.length == 3 ? callback : bounds, bounds = arguments.length == 3 ? bounds : null; self.geocoder.geocode({ latLng: latlng }, function(results, status) { //Ox.print('results', results) var length = results.length; if (status == google.maps.GeocoderStatus.OK) { if (bounds) { Ox.forEach(results.reverse(), function(result, i) { if ( i == length - 1 || canContain(bounds, result.geometry.bounds || result.geometry.viewport) ) { callback(new Ox.MapPlace(parseGeodata(results[i]))); return false; } }); } else { callback(new Ox.MapPlace(parseGeodata(results[0]))); } } if ( status == google.maps.GeocoderStatus.OK || status == google.maps.GeocoderStatus.ZERO_RESULTS ) { triggerGeocodeEvent({ latLng: latlng, results: results }); } else { Ox.print('geocode failed:', status); callback(null); } }); } function getPlaceByName(name, callback) { self.geocoder.geocode({ address: name }, function(results, status) { if (status == google.maps.GeocoderStatus.OK) { callback(new Ox.MapPlace(parseGeodata(results[0]))); } if ( status == google.maps.GeocoderStatus.OK || status == google.maps.GeocoderStatus.ZERO_RESULTS ) { triggerGeocodeEvent({ address: name, results: results }); } else { Ox.print('geocode failed:', status); callback(null); } }); } function getPositionByName(name) { var position = -1; Ox.forEach(self.options.places, function(place, i) { if (place.name == name) { position = i; return false; } }); return position; } function getSelectedMarker() { // needed in case self.options.selected // is changed from outside var id = null; if (self.resultPlace && self.resultPlace.selected) { id = self.resultPlace.id; } else { Ox.forEach(self.places, function(place) { if (place.selected) { id = place.id; return false; } }); } return id; } function getSelectedPlace() { return self.options.selected ? getPlaceById(self.options.selected) : null; } function initMap() { getMapBounds(function(mapBounds) { Ox.print('init', mapBounds.getSouthWest(), mapBounds.getNorthEast(), mapBounds.getCenter()) updateFormElements(); self.elevationService = new google.maps.ElevationService(); self.geocoder = new google.maps.Geocoder(); self.maxZoomService = new google.maps.MaxZoomService(); self.center = mapBounds ? mapBounds.getCenter() : new google.maps.LatLng(0, 0); self.zoom = 1; // fixme: should depend on height that.map = self.map = new google.maps.Map(self.$map.$element[0], { center: self.center, disableDefaultUI: true, disableDoubleClickZoom: true, mapTypeId: google.maps.MapTypeId[getMapType()], zoom: self.zoom }); google.maps.event.addListener(self.map, 'bounds_changed', boundsChanged); google.maps.event.addListener(self.map, 'center_changed', centerChanged); google.maps.event.addListener(self.map, 'click', clickMap); google.maps.event.addListener(self.map, 'idle', mapChanged); google.maps.event.addListener(self.map, 'zoom_changed', zoomChanged); google.maps.event.addListenerOnce(self.map, 'tilesloaded', tilesLoaded); mapBounds && self.map.fitBounds(mapBounds); self.places = []; if (!self.isAsync) { self.options.places.forEach(function(place, i) { self.places[i] = new Ox.MapPlace(Ox.extend({ map: that }, place)); }); } google.maps.event.trigger(self.map, 'resize'); //that.gainFocus(); that.triggerEvent('load'); }); function tilesLoaded() { // fixme: can add earlier, use don't replace map contents option Ox.forEach(self.$navigationButtons, function(button) { button.appendTo(self.$map); }); self.$scaleLabel.appendTo(self.$map); } } function mapChanged() { // gets called after panning or zooming Ox.print('mapChanged'); var bounds; if (self.boundsChanged) { var bounds = self.map.getBounds(), southWest = bounds.getSouthWest(), northEast = bounds.getNorthEast(), south = southWest.lat(), west = southWest.lng(), north = northEast.lat(), east = northEast.lng(), crossesDateline = west > east; if (!self.isAsync) { self.places.sort(function(a, b) { var sort = { a: a.selected ? Infinity : bounds.contains(a.center) ? a.area : -Infinity, b: b.selected ? Infinity : bounds.contains(b.center) ? b.area : -Infinity, }; return sort.b - sort.a; }).forEach(function(place, i) { if (i < self.options.maxMarkers && !place.visible) { place.add(); } else if (i >= self.options.maxMarkers && place.visible) { place.remove(); } }); } else { Ox.print('QUERY', { conditions: Ox.merge([ {key: 'lat', value: [south, north], operator: '-'} ], !crossesDateline ? [ {key: 'lng', value: [west, east], operator: '-'} ] : [ { conditions: [ {key: 'lng', value: west, operator: '<'}, {key: 'lng', value: east, operator: '>'} ], operator: '|' } ]), operator: '&' }); self.options.places({ keys: self.placeKeys, query: { conditions: Ox.merge([ {key: 'lat', value: [south, north], operator: '-'} ], !crossesDateline ? [ {key: 'lng', value: [west, east], operator: '-'} ] : [ {key: 'lng', value: [east, west], operator: '!-'} ]), operator: '&' }, range: [0, self.options.maxMarkers], sort: [{key: 'area', operator: '-'}] }, function(result) { Ox.print('RESULT', result) var ids = []; result.data.items.forEach(function(item, i) { var place = getPlaceById(item.id); if (!place) { self.places.push( new Ox.MapPlace(Ox.extend({ map: that }, item)).add() ); } else if (!place.visible) { place.add(); } ids.push(item.id); }); self.places.forEach(function(place) { if ( !place.selected && place.visible && ids.indexOf(place.id) == -1 ) { place.remove(); } }); }); } self.boundsChanged = false; } if (self.centerChanged) { getMaxZoom(function(zoom) { if (zoom && zoom != self.maxZoom) { self.maxZoom = zoom; if (self.map.getZoom() > zoom) { self.map.setZoom(zoom); } constructZoomInput(); } }); self.centerChanged = false; } if (self.zoomChanged) { self.zoomChanged = false; } } function pan(x, y) { self.map.panBy(x * self.$map.width() / 2, y * self.$map.height() / 2); }; function parseGeodata(data) { var bounds = data.geometry.bounds || data.geometry.viewport, northEast = bounds.getNorthEast(), southWest = bounds.getSouthWest(), place = { alternativeNames: [], components: data.address_components, countryCode: getCountryCode(data.address_components), east: northEast.lng(), editable: self.options.editable, fullGeoname: getFullGeoname(data.address_components), geoname: data.formatted_address, id: '_' + Ox.encodeBase32(Ox.uid()), map: that, name: data.formatted_address.split(', ')[0], north: northEast.lat(), south: southWest.lat(), type: getType(data.address_components[0].types), west: southWest.lng() }; if (Math.abs(place.west) == 180 && Math.abs(place.east) == 180) { place.west = -179.99999999; place.east = 179.99999999; } place.south = Ox.limit(place.south, Ox.MIN_LATITUDE, Ox.MAX_LATITUDE - 0.00000001); place.north = Ox.limit(place.north, Ox.MIN_LATITUDE + 0.00000001, Ox.MAX_LATITUDE); function getCountryCode(components) { countryCode = ''; Ox.forEach(components, function(component) { if (component.types.indexOf('country') > -1) { countryCode = component.short_name; return false; } }); return countryCode; } function getFullGeoname(components) { var country = false; return components.map(function(component, i) { var name = component.long_name; if (i && components[i - 1].types.indexOf('country') > -1) { country = true; } return !country && ( i == 0 || name != components[i - 1].long_name ) ? name : null; }).join(', '); } function getType(types) { Ox.print('getType', types) // see http://code.google.com/apis/maps/documentation/javascript/services.html#GeocodingAddressTypes var strings = { 'country': ['country'], 'region': ['administrative_area', 'colloquial_area'], 'city': ['locality'], 'borough': ['neighborhood', 'postal_code', 'sublocality'], 'street': [ 'intersection', 'route', 'street_address', 'street_number' ], 'building': [ 'airport', 'establishment', 'floor', 'premise', 'room', 'subpremise' ] }, type = 'feature'; Ox.forEach(strings, function(values, key) { Ox.forEach(values, function(value) { if (find(value)) { type = key; return false; } }); return type == 'feature'; }); return type; function find(type) { var ret; Ox.forEach(types, function(v) { ret = Ox.startsWith(v, type); return !ret; }); return ret; } } return place; } function pressEnter() { var place = getSelectedPlace(); if (place) { if (place.editing) { place.submit(); } else { place.edit(); } } else if (self.resultPlace) { selectPlace(self.resultPlace.id) } } function pressEscape() { var place = getSelectedPlace(); if (place) { if (place.editing) { place.cancel(); } else { selectPlace(null); } } else if (self.resultPlace) { self.resultPlace.remove(); self.resultPlace = null; } } function removePlace() { var place = getSelectedPlace(); place.id = '_' + place.id; self.options.selected = place.id; //Ox.print('removePlace', Ox.getObjectById(self.places, place.id)) self.places.splice(Ox.getPositionById(self.places, place.id), 1); self.resultPlace && self.resultPlace.remove(); self.resultPlace = place; place.marker.update(); } function reset() { //Ox.print(self.map.getZoom(), self.zoom); self.map.getZoom() == self.zoom ? self.map.panTo(self.center) : self.map.fitBounds(self.bounds); } function resizeMap() { /* Ox.print('resizeMap', self.options.width, self.options.height); var center = self.map.getCenter(); self.mapHeight = getMapHeight(); self.minZoom = getMinZoom(); that.css({ height: self.options.height + 'px', width: self.options.width + 'px' }); self.$map.css({ height: self.mapHeight + 'px', width: self.options.width + 'px' }); google.maps.event.trigger(self.map, 'resize'); self.map.setCenter(center); */ } function selectPlace(id) { // id can be null (deselect) var place, selected = getSelectedMarker(); Ox.print('Ox.Map selectPlace()', id, selected); if (id != selected) { place = getPlaceById(selected); place && place.deselect(); if (id !== null) { place = getPlaceById(id); if (place) { select(); } else { // async && place doesn't exist yet self.options.places({ keys: self.placeKeys, query: { conditions: [ {key: 'id', value: id, operator: '='} ] } }, function(results) { place = new Ox.MapPlace(Ox.extend({ map: that }, results.data.items[0])).add(); self.places.push(place); select(); that.panToPlace(); }); } } else { place = null; select(); } } function select() { place && place.select(); self.options.selected = id; setStatus(); that.triggerEvent('selectplace', place); } }; function setScale() { var metersPerPixel = getMetersPerPixel(); Ox.forEach(self.scaleMeters, function(meters) { var scaleWidth = Math.round(meters / metersPerPixel); if (scaleWidth <= self.options.width / 2 - 4) { self.$scaleLabel .options({ title: '\u2190 ' + ( meters > 1000 ? Ox.formatNumber(meters / 1000) + ' k' : meters + ' ' ) + 'm \u2192' }) .css({ width: (scaleWidth - 16) + 'px' }) return false; } }); } function setStatus() { //Ox.print('setStatus()', self.options.selected) var code, country, disabled, place, title; if (self.options.statusbar) { place = getSelectedPlace(); country = place ? Ox.getCountryByGeoname(place.geoname) : ''; code = country ? country.code : 'NTHH'; disabled = place && !place.editable; if (place) { title = place.id[0] == '_' ? 'Add Place' : 'Remove Place'; } else { title = 'New Place'; } self.$placeFlag.attr({ src: Ox.PATH + 'Ox.Geo/png/icons/16/' + code + '.png' }); self.$placeNameInput.options({ disabled: disabled, value: place ? place.name : '' }); self.$placeGeonameInput.options({ disabled: disabled, value: place ? place.geoname : '' }); self.$placeButton.options({ disabled: disabled, title: title }); } //Ox.print('STATUS DONE'); } function submitFind(event, data) { that.findPlace(data.value, function(place) { setStatus(place); }); } function toggleLabels() { self.options.showLabels = !self.options.showLabels //Ox.print('toggle', getMapType()) self.map.setMapTypeId(google.maps.MapTypeId[getMapType()]); /* self.$labelsButton.options({ title: self.$labelsButton.options('title') == 'Show Labels' ? 'Hide Labels' : 'Show Labels' }); */ } function triggerGeocodeEvent(data) { // someone may want to cache google geocode data, so we fire an event. // google puts functions like lat or lng on the objects' prototypes, // so we create properly named properties, for json encoding if (data.latLng) { data.latLng = { lat: data.latLng.lat(), lng: data.latLng.lng() } } data.results.forEach(function(result) { ['bounds', 'viewport'].forEach(function(key) { if (result.geometry[key]) { result.geometry[key] = { northEast: { lat: result.geometry[key].getNorthEast().lat(), lng: result.geometry[key].getNorthEast().lng() }, southWest: { lat: result.geometry[key].getSouthWest().lat(), lng: result.geometry[key].getSouthWest().lng() } } } }); if (result.geometry.location) { result.geometry.location = { lat: result.geometry.location.lat(), lng: result.geometry.location.lng() } } }); that.triggerEvent('geocode', data); } function undo() { Ox.print('Map undo') var place = getSelectedPlace(); place.editing && place.undo(); } function updateFormElements() { var width = that.width(); self.$zoomInput && constructZoomInput(); if (self.options.statusbar) { self.$placeNameInput.options({ width: Math.floor((width - 132) / 2) }); self.$placeGeonameInput.options({ width: Math.ceil((width - 132) / 2) }); } } function zoom(z) { self.map.setZoom(self.map.getZoom() + z); } function zoomChanged() { var zoom = self.map.getZoom(); if (zoom < self.minZoom) { self.map.setZoom(self.minZoom); } else if (self.maxZoom && zoom > self.maxZoom) { self.map.setZoom(self.maxZoom); } else { self.zoomChanged = true; self.$zoomInput && self.$zoomInput.options({value: zoom}); that.triggerEvent('zoom', { value: zoom }); } } function zoomToPlace() { Ox.print('zoomToPlace') if (self.options.selected !== null) { self.map.fitBounds(getPlaceById(self.options.selected).bounds); } } self.setOption = function(key, value) { /*if (key == 'height' || key == 'width') { resizeMap(); } else */if (key == 'places') { //fixme: should zoom to new bounds zoom(0); } else if (key == 'selected') { selectPlace(value); } else if (key == 'type') { } }; that.addPlace = function(data) { addPlaceToPlaces(data); }; that.getKey = function() { var key = null; if (self.shiftKey) { key = 'shift' } else if (self.metaKey) { key = 'meta' } return key; }; that.getSelectedPlace = function() { return getSelectedPlace(); } that.editPlace = function() { getSelectedPlace().edit(); return that; }; /* that.editPlace = function(data) { var place = getPlaceById(self.options.selected); place.$marker.options(data); return that; }; */ that.findPlace = function(name, callback) { getPlaceByName(name, function(place) { if (place) { addPlaceToMap(place); self.map.fitBounds(place.bounds); } else { self.$findInput.addClass('OxError'); } callback(place); }); }; that.newPlace = function(place) { addPlaceToMap(place); }; that.panToPlace = function() { Ox.print('panToPlace:', self.options.selected) var place = getSelectedPlace(); place && self.map.panTo(place.center); return that; }; that.removePlace = function() { // fixme: removePlaceFromPlaces() ? removePlace(); return that; }; that.resizeMap = function() { /* Ox.print('resizeMap', self.options.width, self.options.height); var center = self.map.getCenter(); self.mapHeight = getMapHeight(); self.minZoom = getMinZoom(); that.css({ height: self.options.height + 'px', width: self.options.width + 'px' }); self.$map.css({ height: self.mapHeight + 'px', width: self.options.width + 'px' }); google.maps.event.trigger(self.map, 'resize'); self.map.setCenter(center); */ /* Ox.print('Ox.Map.resizeMap()'); var center = self.map.getCenter(); self.options.height = that.$element.height(); self.options.width = that.$element.width(); Ox.print(self.options.width, self.options.height) self.$map.css({ height: self.mapHeight + 'px', width: self.options.width + 'px' }); google.maps.event.trigger(self.map, 'resize'); self.map.setCenter(center); self.options.zoombar && self.$zoomInput.options({ size: self.options.width }); */ updateFormElements(); google.maps.event.trigger(self.map, 'resize'); return that; } that.value = function(id, key, value) { // fixme: should be like the corresponding List/TextList/etc value function Ox.print('Map.value', id, key, value) getPlaceById(id).options(key, value); } that.zoomToPlace = function() { Ox.print('zoomToPlace') var place = getSelectedPlace(); place && self.map.fitBounds(place.bounds); return that; }; that.zoom = function(value) { zoom(value); return that; }; return that; };