I spent this morning searching something simple but same time flexible to use for courier delivery like small companies or groups / to organize them selves and order/destinations in a map. While I gave up for searching after few hours going through trillion posts :) and reviewing many commercial software's. I spent last hour making my own and got my self working application however many questions raised in my mind so I was thinking to start opensource project of it and with this short intro and following code to share it with you guys, hoping that perhaps I find some good answers or great ideas. How to make it efficient to use on any browser, any device including over Mobile Internet . :).
I'm stuck with finding proper way how to keep map up to date basically in real-time.
setInterval!? Anyhow all my browsers freeze and stop updating after some period of time while I expect it to run 24/7/365 if needed. I would really appreciated if somebody can lead me at least right direction to make this sync more stable.
thanks advance!
Clobal Gonfig
/***********************************
* Config
************************************/
/* Master Server */ var master_server = {
url : 'https://0.0.0.0/',
status : false,
connectionFail : "Connection to Master Server Failed"
}
/* Slave Server */ var slave_server = {
url : 'https://0.0.0.0/',
status : false,
connectionFail : "Connection to Slave Server Failed"
}
/* Sync controller */ var sync_controller = '/datasync/';
/* Sync actions */
var sync_action = {
updateLatLng : 'updatewithgeo/'
}
/***********************************/
var car = false;
var order = false;
var order_icon = "static_icon.png";
var customMsg = {
PositionFail : "Because Default lat lng and getCurrentPosition are not set app will not try to sync! It will try again shotwhile"
}
// Can define default lat lng
var lat = false;
var lng = false;
/***********************************/
App
function syncData()
{
//Temporarly clearing markers to avoid marker[$i] to be duplicated if position is changed
if(markers){$('#map_canvas').gmap('clear', 'markers');}
// Exhange data with server
$('#map_canvas').gmap('getCurrentPosition', function(position, status) {
if ( status === 'OK' ) {
lat = position.coords.latitude;
lng = position.coords.longitude;
}
if (!lat){
console.log(customMsg.PositionFail);
setTimeout('syncData()', 10000);
throw new Error(customMsg.PositionFail);
return;
}
$.post(master_server.url+sync_controller+sync_action.updateLatLng, {latlng : lat+"#"+lng}, function(data, Server_1_Status) {
if(Server_1_Status != 'OK')
{
// If request to master server then here would be place to connect to slave server(s)
console.log(master_server.connectionFail);
setTimeout('syncData()', 10000);
throw new Error(master_server.connectionFail);
return;
}
// If there is no errors
$.each( data.couriers, function(i, courier) {
// If markers are identified correctly when they are returned from server
// then addMarker should only add new markers and
// existing markers shuld be updated (setPosition)
$('#map_canvas').gmap('addMarker', {
'position': new google.maps.LatLng(courier.latitude, courier.longitude),
'bounds': true,
'icon' : courier.icon,
'title' : courier.name
}).click(function() {
$('#map_canvas').gmap('openInfoWindow', { 'content': courier.content }, this);
});
});
$.each( data.orders, function(i, order) {
$('#map_canvas').gmap('addMarker', {
'position': new google.maps.LatLng(order.latitude, order.longitude),
'bounds': true,
'title' : order.address,
'icon' : ""
}).click(function() {
// Populating order view and forms to take / confirm delivery and send digital signature
$('#OrderViewTitle').html('<h1>'+order.orderID+order.address+'</h1>');
$('#OrderData').html('<p><b>'+order.customerID+'</b><br><b>Order:</b>'+order.salesorder+'</p>');
document.getElementById('TakeDelivery').value = order.orderID;
document.getElementById('DeliveryComplete').value = order.orderID;
$('#OrderView').popup("open");
});
});
}, "json");
$('#map_canvas').gmap('refresh');
setTimeout('syncData()', 10000);
});
}
$(document).ready(function() {
courierApp.add(function() {
$('#map_canvas').gmap().bind('init', function() {
syncData();
});
}).load();
});
Related
Just discovered Leafletjs and loving it. I have been trying to remove all my makers when my json is empty or invalid and I just cant get it right. All the different approches I have tried blink/flash every time my json updates and this is the closest I have managed to get.
Any help would be greatfull. I am taken back at how little examples there are of makers moving and updating without blinking.... and I really dont want to use google maps!
I tried to reset the makers = {}; but this did nothing.
Thank you
data.BMS.forEach(function (obj) {
if (obj.lat !== undefined && obj.lng !== undefined) {
if (!markers.hasOwnProperty(obj.id)) {
markers[obj.id] = new L.Marker([obj.lat, obj.lng], {icon: panicNormal}).addTo(map) .bindTooltip(obj.name,
{
permanent: true,
direction: 'top',
offset: [0, 0]
});
markers[obj.id].previousLatLngs = [];
areaBounds.push([obj.lat, obj.lng]);
} else {
areaBounds.push([obj.lat, obj.lng]);
markers[obj.id].previousLatLngs.push(markers[obj.id].getLatLng());
if(obj.status == "TRUE"){
markers[obj.id].setIcon(panicAlarm);
}else{
if(obj.type == "MO"){
markers[obj.id].setIcon(panicNormal);
}else{
markers[obj.id].setIcon(lora);
}
}
markers[obj.id].setLatLng([obj.lat, obj.lng]);
}
}else{
//How do I remove the markers
}
});
You can use L.FeatureGroup() to add all markers to it and then remove all markers with .clearLayers()
var fg = L.featureGroup().addTo(map);
...
markers[obj.id] = new L.Marker([obj.lat, obj.lng], {icon: panicNormal}).addTo(fg) .bindTooltip
...
}else{
//How do I remove the markers
fg.clearLayers();
}
I am trying to print the map that I create on the fly. However the image that I am getting from the map canvas sometimes looks half baked:
In the screen shot, the top portion is the openlayers map that eventually got rendered and the bottom portion is what I captured from the canvas after postrender.
I am starting from the official Export PDF example, but I am working with a map that just got created. I keep a track of the tiles loading and the tiles loaded through the tileloadstart and tileloadend map events. I also tried incorporating the postrender event as per this answer on stackoverflow. When all the tiles have been loaded I wait for the postrender event to capture the map image from the canvas.
The problem:
I think the problem has to do with the postrender event firing off two early and I don't know how much time to wait after the event before capturing the image from the canvas. I started with a delay of 100 milliseconds and then moved to 1 second and it seemed like it was working fine. But if I add more layers I had to take the delay to 3 seconds for it to start behaving correctly. So I don't know what the delay should be for maps with a lot of layers.
I also get the problem that sometimes the tileloadstart and tileloadend event keeps on firing even after postrender because some tiles had yet to be loaded and even though the tile load count had equalized.
I have created a codepen to demonstrate the issue:
Print map using tile loading and postrender events
var bingKey = ""; // Please enter a bing key
var exportButton = document.getElementById("prepareMap");
exportButton.addEventListener("click", function() {
exportButton.disabled = true;
document.body.style.cursor = "progress";
prepareMap();
});
var map;
var mapCanvas;
var printDelayAfterLoad = 100;
var loading;
var loaded;
var allLoaded;
var prepareMap = function() {
loading = 0;
loaded = 0;
allLoaded = false;
var raster = new ol.layer.Tile({
source: new ol.source.OSM(),
opacity: 0.7
});
var bingLayer = new ol.layer.Tile({
source: new ol.source.BingMaps({
key: bingKey,
imagerySet: "Aerial"
})
});
var mapLayers = [bingLayer, raster];
map = new ol.Map({
layers: mapLayers,
target: "map",
controls: ol.control.defaults({
attributionOptions: {
collapsible: false
}
}),
view: new ol.View({
center: [0, 0],
zoom: 2
})
});
for (var i = 0; i < mapLayers.length; i++) {
var layerSource = mapLayers[i].getSource();
layerSource.on("tileloadstart", tileLoadStart);
layerSource.on("tileloadend", tileLoadEnd);
layerSource.on("tileloaderror", tileLoadEnd);
}
map.renderSync();
};
var tileLoadStart = function() {
++loading;
if (allLoaded) {
logExtraTilesLoadError();
}
};
var tileLoadEnd = function() {
++loaded;
if (loading === loaded) {
allLoaded = true;
listenToPostRender();
}
};
var alreadyPrinted = false;
var listenToPostRender = function() {
map.once("postrender", function() {
if (!alreadyPrinted) {
console.log("postrender called");
window.setTimeout(function() {
printMap();
}, printDelayAfterLoad);
alreadyPrinted = true;
}
});
// listening to postcompose just to capture the mapCanvas from the event
map.once("postcompose", function(event) {
console.log("postcompose called");
mapCanvas = event.context.canvas;
});
};
var printMap = function() {
console.log("printMap called");
var data = mapCanvas.toDataURL("image/png");
var mapImageElement = document.getElementById("mapImage");
mapImageElement.setAttribute("src", data);
cleanUp();
};
var cleanUp = function() {
console.log("cleanUp called");
//exportButton.disabled = false;
document.body.style.cursor = "auto";
};
var errorMessage =
" more tiles loaded after initially all tiles had been loaded";
var extraTileLoadCount = 0;
var logExtraTilesLoadError = function() {
extraTileLoadCount++;
var errorsDiv = document.getElementById("errorsDiv");
errorsDiv.innerHTML = extraTileLoadCount + errorMessage;
console.error("errorMessage");
};
You would need to enter a Bing Maps Key at the top of the javascript file. A Bing Map Key can be generated from: https://www.bingmapsportal.com/. With the BingMap layer the problem is reproducible more consistently. In the code pen I am also capturing the problem when tileloadstart is called after the postrender and writing an error to the top of the page. Also please note that the behavior is inconsistent and you might have to reload several times for it to reproduce.
it's the first time I tried to animate polygon points with snap.svg and I have the felling I'm doing something wrong there.
Here is my code :
var fdr = Snap('#fdright');
var time1_stp0 = [363.617,262.895, 363.562,367.4, 273.145,315.191, 273.145,315.191];
var time1_stp1 = [363.617,262.895, 363.562,367.4, 273.145,315.191, 273.145,210.688];
var timeline1 = fdr.polygon(time1_stp0).attr({fill:'red',opacity:'0.5'});
timeline1_anim = function(){
timeline1.animate({"points":time1_stp1},3000,mina.linear);
}
timeline1_anim();
As soon as the page is loaded, my polygon disappears (I guess it's because my function is called right after the creation of the polygon). I checked the html, my polygon's still there but here is what i get :
<polygon fill="#ff0000" style="opacity: 0.5;" points="363.617"></polygon>
I don't get what might be the issue, so if someone's got an answer i'll be glad to hear it.
EDIT : I tried to add "toString()" but it's still not working :
timeline1_anim = function(){
timeline1.animate({"points":time1_stp1.toString()},3000,mina.linear);
}
I think there's a bug in Snaps polygon animation.. its listed here There is a patch submitted linked from there.
However, you can get around this easily by animating the array values though if needed.
timeline1_anim = function(){
Snap.animate(time1_stp0, time1_stp1,
function( val ){
timeline1.attr({ points: val })
},
2000);
}
jsfiddle
If you are doing a lot of them, you could write a small plugin to include it...
Snap.plugin( function( Snap, Element, Paper, global ) {
Element.prototype.polyAnimate = function( destPoints, duration, easing, callback ) {
var poly = this;
Snap.animate( this.attr('points'), destPoints,
function( val ){ poly.attr({ points: val }) }, duration, easing, callback)
};
});
timeline1.polyAnimate( time1_stp1, 2000, mina.linear, function() { alert('finished')})
jsfiddle
When I get panorama for marker within 50 meter max radius then sometimes it finds no panorama but if I'd set about 60+ meter radius then it would have worked.
So I'd like to auto make another request for street data with +10 meters radius and if nothing was found up to 100 meters total then stop completely.
I know how to loop it in procedural JS with for or while whatever, but I don't know OOP JS so I am not sure where/how to fire request again based on function response.
Basically code looks like
marker1.addListener('click', function(event) {
popup1.open(map, marker1);
sv.getPanorama({location: event.latLng, radius: 50}, processSVData);
});
...
function processSVData(data, status) {
if (status === google.maps.StreetViewStatus.OK) {
panorama.setPano(data.location.pano);
panorama.setPov({
heading: 270,
pitch: 0
});
panorama.setVisible(true);
} else if (status === google.maps.StreetViewStatus.ZERO_RESULTS) {
alert(status + " need to retry with radius +10 merters but how?");
} else {
alert(status + " cant do nothing about it bye");
}
}
Here's full example marker B has street view data at 50 meters when marker A does not.
JSFiddle
Assuming that there is no conflict elsewhere in the program with the panorama queries calling too quickly (going over query limit), then a possible solution would be to use a function with a timeout to handle the query.
function getPanorama(args){
setTimeout(function(){
sv.getPanorama(args, function(data, status){processSVData(data, status, args);
//need to pass args to callback to properly increment
}, query_limit_delay);
}
and in processSVData:
function processSVData(data, status, args){
//code
} else if (status === google.maps.StreetViewStatus.ZERO_RESULTS) {
if(args.radius < 100){
args.radius += 10; //increase radius by 10 meters
getPanorama(args);
}
}
//code
}
Basically, this 1) calls the getPanorama function. Once the delay (to not go over the query limit) has passed, it sends the returned data to the callback. If there are ZERO_RESULTS, the radius from the previous query is increased by 10, and the process repeats. However, if the data shows up, the data is handled and the loop exits.
One initiates a query by calling something like:
marker1.addListener('click', function(event) {
popup1.open(map, marker1);
getPanorama({location: event.latLng, radius: 50});
});
Good luck on your application!
If there's a query limit to this API, I haven't reached it yet. I implemented a recursive function. Try something like:
marker1.addListener('click', function(event) {
popup1.open(map, marker1);
processSVData(event.latLng, 50); // first try 50 meters
});
function processSVData(loc,rad) {
sv.getPanorama({location:loc, radius:rad}, function(data,status){
if (status===google.maps.StreetViewStatus.ZERO_RESULTS) {
processSVData(loc,rad+10); // increment by 10 meters
} else if (status===google.maps.StreetViewStatus.OK) {
// Success!
panorama.setPano(data.location.pano);
panorama.setPov({
heading: 270,
pitch: 0
});
panorama.setVisible(true);
} else {
// Failure
alert(status + " cant do nothing about it bye");
}
});
}
I get this error:
[ERROR][GeolocationModule( 278)] (KrollRuntimeThread) [633,2564] Unable to get current position, location is null
and I have followed other people's advice without any luck.
Could someone lead me in the right direction? I would be so grateful. Thank you!
var win1 = Titanium.UI.createWindow({
title : 'map_landing',
backgroundColor : '#fff'
});
var win2 = Titanium.UI.createWindow({
title : 'hails_window',
backgroundColor : '#fff'
});
var win3 = Titanium.UI.createWindow({
title : 'cabs_window',
backgroundColor : '#fff'
});
User = {
location : {}
};
var hail_button = Titanium.UI.createButton({
title : 'Hail Cab',
top : 10,
width : 200,
height : 50
});
var find_button = Titanium.UI.createButton({
title : 'Find People',
bottom : 10,
width : 200,
height : 50
});
var options = {
accessKeyId : '',
secretAccessKey : ''
}
Ti.Geolocation.purpose = "Receive user location";
Titanium.Geolocation.getCurrentPosition(function(e) {
if (e.error) {
alert('HFL cannot get your current location');
return;
}
User.location.longitude = e.coords.longitude;
User.location.latitude = e.coords.latitude;
User.location.accuracy = e.coords.accuracy;
User.location.speed = e.coords.speed;
User.location.timestamp = e.coords.timestamp;
var mapview = Titanium.Map.createView({
mapType : Titanium.Map.STANDARD_TYPE,
region : {
latitude : User.location.latitude,
longitude : User.location.longitude,
latitudeDelta : 0.01,
longitudeDelta : 0.01
},
animate : true,
regionFit : true,
userLocation : true
});
win1.add(mapview);
win1.add(hail_button);
win1.add(find_button);
hail_button.addEventListener('click', function(e) {
alert('hello');
$.ajax('http://hail.pagodabox.com/add_hail', {
type : 'POST',
lat : User.location.latitude,
lang : User.location.longitude,
success : function(response) {
alert(response)
}
})
});
find_button.addEventListener('click', function(e) {
});
win1.open();
});
It took me some time to track this down, but I think I've found the steps needed to "fix" the lack of a location in the Android emulator (from this answer):
First, open ddms (Dalvik Debug Monitor). On Windows, navigate to the
android-sdk\tools directory and run ddms.bat. The first line in the
top-left pane will read something like "emulator-####" such as
emulator-5560.
Open a command prompt window. Enter 'telnet localhost ####'
substituting the number you found above. This will open a telnet
window to your android emulator.
Enter the following command (substitute your own longitude & latitude,
in that order, if you'd like):
geo fix -82.411629 28.054553
(I'm pretty sure you can add elevation as a third number.) The GPS
icon will appear in the emulator's notification bar. Geolocation is
now available. At this point, I could get location data in my app and
in other apps, such as Maps.