NGApp.factory( 'PositionsService', function(){ var service = { bounding: null, locs: [] } /** * Method adds new location to the locs array */ service.addLocation = function( loc ){ // Resets the restaurant's list App.restaurants.forceLoad = true; service.locs.push(loc); $.totalStorage('locsv2', service.locs); } /** * get the most recent position */ service.pos = function() { return ( ( service.locs.length ) ? service.locs[ service.locs.length - 1 ] : new Location ); } return service; } ); // LocationServiceservice NGApp.factory( 'LocationService', function( $location, RestaurantsService, PositionsService ){ var service = { form : { address : '' }, range: App.defaultRange, loaded: false, locationNotServed: false, } service.position = PositionsService; service.restaurantsService = RestaurantsService; /** * calculate the distance between two points */ service.distance = function(params) { try { var R = 6371; // Radius of the earth in km var dLat = _toRad(params.to.lat - params.from.lat); var dLon = _toRad(params.to.lon - params.from.lon); var a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.cos(_toRad(params.from.lat)) * Math.cos(_toRad(params.to.lat)) * Math.sin(dLon/2) * Math.sin(dLon/2); var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); var d = R * c; // Distance in km return d; } catch(e) { var pos = service.position.pos(); App.track('Location Error', { lat: pos.lat, lon:pos.lon, address: pos.address() }); } } /** * get the closest */ service.getClosest = function(results, latLong) { var from = { lat: latLong.lat(), lon: latLong.lng() }; var distances = []; var closest = -1; for (i = 0; i < results.length; i++) { var d = service.distance({ to: {lat: results[i].geometry.location.lat(), lon: results[i].geometry.location.lng()}, from: from }); distances[i] = d; if (closest == -1 || d < distances[closest]) { closest = i; } } return results[closest]; } /** * get location from the browsers geolocation */ service.getLocationByBrowser = function(success, error) { var success = success || function(){}; var error = error || function(){}; if (navigator.geolocation) { service.timerId = setTimeout(function() { error(); }, 5000); navigator.geolocation.getCurrentPosition(function(position){ clearTimeout(service.timerId); service.position.addLocation(new Location({ lat: position.coords.latitude, lon: position.coords.longitude, type: 'geolocation' })); App.track('Locations Shared', { lat: position.coords.latitude, lon: position.coords.longitude }); // get the city from shared location service.reverseGeocode(position.coords.latitude, position.coords.longitude, success, error); }, function() { clearTimeout(service.timerId); error(); }, {maximumAge: 60000, timeout: 5000, enableHighAccuracy: true}); } else { error(); } } /** * initilize location functions */ service.init = function() { // 1) set bounding to maxmind results if we have them if (App.config.loc.lat && App.config.loc.lon) { service.bounding = App.config.loc; service.bounding.type = 'geoip'; } // 2) retrieve and set location date from cookies var cookieLocs = $.totalStorage('locsv2'); var cookieBounding = $.totalStorage('boundingv2'); if ( cookieLocs ) { for( var x in cookieLocs ) { service.position.addLocation(new Location(cookieLocs[x]._properties)); } } if ( cookieBounding ) { service.bounding = cookieBounding; service.bounding.type = 'cookie'; } // 3) set location info by stored user if (App.config && App.config.user && App.config.user.location_lat) { service.bounding = { lat: App.config.user.location_lat, lon: App.config.user.location_lon, type: 'user' }; } // 4) get a more specific bounding location result from google if (google && google.load) { google.load('maps', '3', {callback: service.googleCallback, other_params: 'sensor=false'}); } } // TODO I changed this method just to make it work, it is not ready yet // callback for google location api service.googleCallback = function() { console.debug('PROCESSING LOCATION DATA FROM GOOGLE API'); // if we dont have the proper location data, just populate from bounding var error = function() { if (service.bounding && service.bounding.lat && service.bounding.lon && !service.bounding.city) { service.reverseGeocode(service.bounding.lat, service.bounding.lon, // Success function( loc ) { service.bounding.city = loc.city(); service.bounding.region = ''; service.position.addLocation( loc ); service.restaurantsService.list( // Success function(){ $location.path( '/' + App.restaurants.permalink ); }, // Error function(){ // alert( 'error' ); } ); }, // Error function(){}); } } service.loaded = true; if (google.loader.ClientLocation) { // we got a location back from google. use it if (google.loader.ClientLocation.latitude && google.loader.ClientLocation.longitude) { service.bounding = { lat: google.loader.ClientLocation.latitude, lon: google.loader.ClientLocation.longitude, city: google.loader.ClientLocation.address.city, region: google.loader.ClientLocation.address.country_code == 'US' && google.loader.ClientLocation.address.region ? google.loader.ClientLocation.address.region.toUpperCase() : google.loader.ClientLocation.address.country_code, type: 'googleip' }; } } // 5) if there is no previously used locations of any kind if (!service.position.locs.length) { service.getLocationByBrowser(function(){}, error); } else { error(); } } /** * geocode an address and perform callbacks */ service.geocode = function(address, success, error) { service.doGeocode(address, function(results) { if (results.alias) { var loc = new Location({ address: results.alias.address(), entered: address, type: 'alias', lat: results.alias.lat(), lon: results.alias.lon(), city: results.alias.city(), prep: results.alias.prep() }); } else { // if we have a bounding result, bind to it if (service.bounding) { var latLong = new google.maps.LatLng(service.bounding.lat, service.bounding.lon); var closest = service.getClosest(results, latLong); } else { var closest = results[0]; } var loc = new Location({ results: results, entered: address, type: 'user', lat: closest.geometry.location.lat(), lon: closest.geometry.location.lng() }); } success(loc); }, function() { error(); }); } /** * process the geocode result */ service.doGeocode = function(address, success, error) { address = $.trim(address); App.track('Location Entered', { address: address }); var rsuccess = function(results) { success(results); }; // there was no alias, do a real geocode var rerror = function() { var params = { address: address }; // if we have a bounding result, process based on that if (service.bounding) { var latLong = new google.maps.LatLng(service.bounding.lat, service.bounding.lon); // Create a cicle bounding box var circle = new google.maps.Circle({center: latLong, radius: App.boundingBoxMeters}); var bounds = circle.getBounds(); params.bounds = bounds; } // Send the request out to google var geocoder = new google.maps.Geocoder(); geocoder.geocode(params, function(results, status) { if (status == google.maps.GeocoderStatus.OK) { success(results, status); } else { error(results, status); } }); }; // TODO: use the service App.routeAlias(address, rsuccess, rerror); } /** * perform a reverse geocode from lat/lon */ service.reverseGeocode = function(lat, lon, success, error) { App.track('Location Reverse Geocode', { lat: lat, lon: lon }); var geocoder = new google.maps.Geocoder(); var latlng = new google.maps.LatLng( lat, lon ); geocoder.geocode({'latLng': latlng}, function(results, status) { if (status == google.maps.GeocoderStatus.OK) { if (results[1]) { success(new Location({ results: results, lat: lat, lon: lon })); } else { error(); } } else { error(); } }); } // This method validate the acceptables types of address/location service.validateAddressType = function( addressLocation ){ // Check if the address is rooftop or range_interpolated if( addressLocation && addressLocation.geometry && addressLocation.geometry.location_type && ( addressLocation.geometry.location_type == google.maps.GeocoderLocationType.ROOFTOP || addressLocation.geometry.location_type == google.maps.GeocoderLocationType.RANGE_INTERPOLATED ) ){ return true; } // If the address is not rooftop neither range_interpolated it could be approximate if( addressLocation && addressLocation.geometry && addressLocation.geometry.location_type && ( addressLocation.geometry.location_type == google.maps.GeocoderLocationType.APPROXIMATE ) ){ // The address type could be premise, subpremise, intersection or establishment for ( var x in addressLocation.types ) { var addressType = addressLocation.types[ x ]; if( addressType == 'premise' || addressType == 'subpremise' || addressType == 'intersection' || addressType == 'establishment' ){ return true; } } } // It is not valid return false; } /** * convert killometers to miles */ service.km2Miles = function(km) { return km * 0.621371; } /** * convert miles to killometers */ service.Miles2Km = function(miles){ return miles * 1.60934; } /** * verify a location, and add to the location stack if nessicary */ service.addVerify = function() { if (arguments[0] && typeof arguments[1] != 'function') { // its lat/lon var success = arguments[2]; var error = arguments[3]; } else { // its text based var address = arguments[0]; var success = arguments[1]; var error = arguments[2]; for (var x in service.locs) { if (service.locs[x].entered == address && service.locs[x].verified) { success(service.locs[x]); return; } } service.geocode(address, function(loc) { service.position.addLocation(loc); success(); $.totalStorage('boundingv2', service.bounding); }, error); } //service.geocode(address, success, error); /* App.log.location( { 'address' : service.address(), 'lat' : service.pos().lat, 'lon' : service.pos().lon } , 'address not served' ); App.track('Location Error', { lat: service.pos().lat, lon: service.pos().lon, address: service.address() }); */ } return service; } );