629 lines
16 KiB
JavaScript
629 lines
16 KiB
JavaScript
NGApp.factory('PositionsService', function ( $rootScope ) {
|
|
|
|
var service = {
|
|
bounding: null,
|
|
locs: [],
|
|
storageLimit : 4
|
|
}
|
|
|
|
/**
|
|
* Method adds new location to the locs array
|
|
*/
|
|
service.addLocation = function (loc) {
|
|
service.locs.push(loc);
|
|
service.storeLocations();
|
|
$rootScope.$broadcast( 'NewLocationAdded', true );
|
|
}
|
|
|
|
service.storeLocations = function(){
|
|
// Duplicated positions should not be saved at cookie
|
|
var keys = {};
|
|
service.locs.reverse();
|
|
for( x in service.locs ){
|
|
var key = service.locs[ x ].getKey();
|
|
if( keys[ key ] ){
|
|
service.locs[ x ].markToRemove();
|
|
}
|
|
keys[ key ] = true;
|
|
}
|
|
service.locs.reverse();
|
|
|
|
var locs = [];
|
|
for( x in service.locs ){
|
|
// Stores just the served and not repeated locations
|
|
if( service.locs[x].storeAtCookie() ){
|
|
locs.push( service.locs[x].toCookie() );
|
|
}
|
|
}
|
|
|
|
if( locs.length > service.storageLimit ){
|
|
locs = locs.slice( locs.length - service.storageLimit );
|
|
}
|
|
$.totalStorage( 'locsv3', locs );
|
|
}
|
|
|
|
service.removeNotServedLocation = function(){
|
|
// Mark to remove the last added location - it is not served
|
|
service.locs[ service.locs.length - 1 ].markToRemove();
|
|
service.storeLocations();
|
|
}
|
|
|
|
/**
|
|
* 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, $rootScope, RestaurantsService, PositionsService, AccountService, CommunityAliasService) {
|
|
|
|
var service = {
|
|
form: {
|
|
address: ''
|
|
},
|
|
range: 2,
|
|
boundingBoxMeters : 8000,
|
|
loaded: false,
|
|
locationNotServed: false,
|
|
initied: false,
|
|
loadRestaurantsPage: true,
|
|
initied : false
|
|
}
|
|
|
|
var community = CommunityAliasService;
|
|
|
|
service.account = AccountService;
|
|
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 = service.toRad(params.to.lat - params.from.lat);
|
|
var dLon = service.toRad(params.to.lon - params.from.lon);
|
|
var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
|
Math.cos(service.toRad(params.from.lat)) * Math.cos(service.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 () {
|
|
// force refresh
|
|
|
|
var force = false;
|
|
if (arguments[0]) {
|
|
force = true;
|
|
}
|
|
|
|
// this method could not be called twice
|
|
if (service.initied && !force) {
|
|
return;
|
|
}
|
|
|
|
// 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('locsv3');
|
|
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 (service.account && service.account.user && service.account.user.location_lat) {
|
|
service.bounding = {
|
|
lat: service.account.user.location_lat,
|
|
lon: service.account.user.location_lon,
|
|
type: 'user'
|
|
};
|
|
|
|
service.position.addLocation(new Location({
|
|
lat: service.account.user.location_lat,
|
|
lon: service.account.user.location_lon,
|
|
type: 'user'
|
|
}));
|
|
|
|
service.loadRestaurantsPage = true;
|
|
}
|
|
|
|
if (service.initied) {
|
|
return;
|
|
}
|
|
|
|
service.initied = true;
|
|
|
|
// 4) get a more specific bounding location result from google
|
|
if (App.isPhoneGap) {
|
|
// @todo: id like to use native gelocation if posible at some point
|
|
} else {
|
|
try{
|
|
if( google && google.load && !google.maps ){
|
|
google.load('maps', '3', {
|
|
callback: service.googleCallback,
|
|
other_params: 'sensor=false'
|
|
});
|
|
}
|
|
} catch(e){}
|
|
}
|
|
}
|
|
|
|
// 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 () {
|
|
service.loadRestaurantsPage = true;
|
|
},
|
|
// Error
|
|
function () {
|
|
$rootScope.$broadcast( 'locationError', true );
|
|
});
|
|
},
|
|
// 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 are location
|
|
if( service.position.locs.length ){
|
|
var loc = service.position.pos();
|
|
service.reverseGeocode(loc.lat(), loc.lon(),
|
|
// Success
|
|
function (loc) {
|
|
service.bounding = {
|
|
lat: loc.lat(),
|
|
lon: loc.lon(),
|
|
city: loc.city()
|
|
};
|
|
service.restaurantsService.list(
|
|
// Success
|
|
function () {
|
|
service.loadRestaurantsPage = true;
|
|
},
|
|
// Error
|
|
function () {
|
|
$rootScope.$broadcast( 'locationNotServed', true );
|
|
});
|
|
},
|
|
// Error
|
|
function () {});
|
|
} else
|
|
// 6) if there is no previously used locations of any kind
|
|
if (!service.position.locs.length) {
|
|
service.getLocationByBrowser(function ( loc ) {
|
|
service.bounding = {
|
|
lat: loc.lat(),
|
|
lon: loc.lon(),
|
|
city: loc.city(),
|
|
type: 'geolocation'
|
|
};
|
|
service.position.addLocation(loc);
|
|
service.restaurantsService.list(
|
|
function () {
|
|
service.loadRestaurantsPage = true;
|
|
},
|
|
function () {
|
|
$rootScope.$broadcast( 'locationNotServed', true );
|
|
});
|
|
|
|
}, 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, ignoreRoute) {
|
|
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 && google && google.maps && google.maps.LatLng ) {
|
|
|
|
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: service.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);
|
|
}
|
|
});
|
|
};
|
|
|
|
community.route(address, rsuccess, rerror);
|
|
}
|
|
|
|
service.ordinalReplace = function( address ){
|
|
var ordinals = { 'first' : '1st',
|
|
'second' : '2nd',
|
|
'third' : '3rd',
|
|
'fourth' : '4th',
|
|
'fifth' : '5th',
|
|
'sixth' : '6th',
|
|
'seventh' : '7th',
|
|
'eighth' : '8th',
|
|
'ninth' : '9th',
|
|
'tenth' : '10th',
|
|
'eleventh' : '11th',
|
|
'twelfth' : '12th',
|
|
'thirteenth' : '13th',
|
|
'fourteenth' : '14th',
|
|
'fifteenth' : '15th',
|
|
'sixteenth' : '16th',
|
|
'seventeenth' : '17th',
|
|
'eighteenth' : '18th',
|
|
'nineteenth' : '19th',
|
|
'twentieth' : '20th'
|
|
};
|
|
for( ordinal in ordinals ){
|
|
var regex = new RegExp( '(' + ordinal + ')', 'gi' );
|
|
address = address.replace( regex, ordinals[ordinal] );
|
|
}
|
|
return address;
|
|
}
|
|
|
|
service.doGeocodeWithBound = function(address, latLong, success, error) {
|
|
// Create a cicle bounding box
|
|
var circle = new google.maps.Circle( { center: latLong, radius: service.boundingBoxMeters } );
|
|
var bounds = circle.getBounds();
|
|
|
|
// Send the request out to google
|
|
var geocoder = new google.maps.Geocoder();
|
|
geocoder.geocode({address: address, bounds : bounds }, function(results, status) {
|
|
if (status == google.maps.GeocoderStatus.OK) {
|
|
success(results, status);
|
|
} else {
|
|
error(results, status);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 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();
|
|
}
|
|
});
|
|
}
|
|
|
|
service.theClosestAddress = function (results, latLong) {
|
|
var lat = latLong.lat();
|
|
var lng = latLong.lng();
|
|
var R = 6371;
|
|
var distances = [];
|
|
var closest = -1;
|
|
for (i = 0; i < results.length; i++) {
|
|
var alat = results[i].geometry.location.lat();
|
|
var alng = results[i].geometry.location.lng();
|
|
var dLat = service.toRad(alat - lat);
|
|
var dLong = service.toRad(alng - lng);
|
|
var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
|
Math.cos(service.toRad(lat)) * Math.cos(service.toRad(lat)) * Math.sin(dLong / 2) * Math.sin(dLong / 2);
|
|
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
var d = R * c;
|
|
distances[i] = d;
|
|
if (closest == -1 || d < distances[closest]) {
|
|
closest = i;
|
|
}
|
|
}
|
|
|
|
address = results[closest];
|
|
var loc = new Location({
|
|
type: 'closest',
|
|
lat: address.geometry.location.lat(),
|
|
lon: address.geometry.location.lng()
|
|
});
|
|
loc.setAddressFromResult( address );
|
|
loc.setCityFromResult( address );
|
|
loc.setZipFromResult( address );
|
|
return { result: address, location : loc } ;
|
|
}
|
|
|
|
|
|
// 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;
|
|
}
|
|
|
|
service.toRad = function( number ){
|
|
return number * Math.PI / 180;
|
|
}
|
|
|
|
|
|
/**
|
|
* 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.setItem('boundingv2', service.bounding);
|
|
}, error);
|
|
}
|
|
}
|
|
|
|
return service;
|
|
}); |