I want draggable markers on a Leaflet map and distribute their new locations in realtime to the clients. I use meteor. To achieve this I observe the marker-collection.
This is what I've tried so far but it crashes when I drag one marker. The selected marker disappears as it should but it won't rerender the markersGroup on the map.
var newMarker;
var markers = [];
Happening.find({}).observe({
added: function(marker) {
var myIcon;
myIcon = L.icon({
iconUrl: "icon_33997.svg"
});
newMarker = L.marker([marker.location.coordinates[1],marker.location.coordinates[0]], {
icon: myIcon,
_id: marker._id,
draggable: true
});
newMarker.on('dragend', function (e){
var newCoords = this.getLatLng();
var happeningOld = Happening.find({_id: e.target.options._id}).fetch();
return Happening.update({_id: e.target.options._id}, {
item: happeningOld[0].item,
location: {
type: 'Point',
coordinates: [newCoords.lng, newCoords.lat]
},
time: Date.now(),
owner: happeningOld[0].owner
});
});
markers[newMarker.options._id] = newMarker;
markersGroup.addLayer(newMarker);
return map.addLayer(markersGroup);
},
changed: function(marker){
map.removeLayer(markersGroup);
markersGroup.removeLayer(markers[marker._id]);
markersGroup.addLayer(markers[marker._id]);
return map.addLayer(markersGroup);
}
});
This is the crash-report:
Exception in queued task: L.DistanceGrid.prototype._sqDist#http://localhost:3000/packages/leaflet-markercluster.js?d3d9bebcb9f8a1b1711174aea16a51003ba02d10:36
L.DistanceGrid.prototype.getNearObject#http://localhost:3000/packages/leaflet-markercluster.js?d3d9bebcb9f8a1b1711174aea16a51003ba02d10:36
L.MarkerClusterGroup<._addLayer#http://localhost:3000/packages/leaflet-markercluster.js?d3d9bebcb9f8a1b1711174aea16a51003ba02d10:36
L.MarkerClusterGroup<.addLayers/l<#http://localhost:3000/packages/leaflet-markercluster.js?d3d9bebcb9f8a1b1711174aea16a51003ba02d10:36
o.Util.bind/<#http://localhost:3000/packages/leaflet.js?ad7b569067d1f68c7403ea1c89a172b4cfd68d85:37
L.MarkerClusterGroup<.addLayers#http://localhost:3000/packages/leaflet-markercluster.js?d3d9bebcb9f8a1b1711174aea16a51003ba02d10:36
L.MarkerClusterGroup<.onAdd#http://localhost:3000/packages/leaflet-markercluster.js?d3d9bebcb9f8a1b1711174aea16a51003ba02d10:36
o.Map<._layerAdd#http://localhost:3000/packages/leaflet.js?ad7b569067d1f68c7403ea1c89a172b4cfd68d85:37
o.Map<.addLayer#http://localhost:3000/packages/leaflet.js?ad7b569067d1f68c7403ea1c89a172b4cfd68d85:37
Template.map.rendered/<.changed#http://localhost:3000/where-to-go.js?1b666a1c77f7d81e0212a2c65aa72a9d570b4dac:287
LocalCollection._observeFromObserveChanges/observeChangesCallbacks.changed#http://localhost:3000/packages/minimongo.js?4ee0ab879b747ffce53b84d2eb80d456d2dcca6d:3845
LocalCollection._CachingChangeObserver/self.applyChange.changed#http://localhost:3000/packages/minimongo.js?4ee0ab879b747ffce53b84d2eb80d456d2dcca6d:3750
.observeChanges/wrapCallback/</<#http://localhost:3000/packages/minimongo.js?4ee0ab879b747ffce53b84d2eb80d456d2dcca6d:374
.runTask#http://localhost:3000/packages/meteor.js?148e9381d225ecad703f4b858769b636ff7a2537:576
.flush#http://localhost:3000/packages/meteor.js?148e9381d225ecad703f4b858769b636ff7a2537:604
.drain#http://localhost:3000/packages/meteor.js?148e9381d225ecad703f4b858769b636ff7a2537:612
LocalCollection.prototype.update#http://localhost:3000/packages/minimongo.js?4ee0ab879b747ffce53b84d2eb80d456d2dcca6d:732
#http://localhost:3000/packages/mongo-livedata.js?cf17a2975aa7445f0db2377c2af07e5efc240958:730
.apply/ret<#http://localhost:3000/packages/livedata.js?7f11e3eaafcbe13d80ab0fb510d25d9595e78de2:3818
.withValue#http://localhost:3000/packages/meteor.js?148e9381d225ecad703f4b858769b636ff7a2537:794
.apply#http://localhost:3000/packages/livedata.js?7f11e3eaafcbe13d80ab0fb510d25d9595e78de2:3810
Meteor.Collection.prototype[name]#http://localhost:3000/packages/mongo-livedata.js?cf17a2975aa7445f0db2377c2af07e5efc240958:531
Template.map.rendered/<.added/<#http://localhost:3000/where-to-go.js?1b666a1c77f7d81e0212a2c65aa72a9d570b4dac:272
o.Mixin.Events.fireEvent#http://localhost:3000/packages/leaflet.js?ad7b569067d1f68c7403ea1c89a172b4cfd68d85:37
o.Handler.MarkerDrag<._onDragEnd#http://localhost:3000/packages/leaflet.js?ad7b569067d1f68c7403ea1c89a172b4cfd68d85:40
o.Mixin.Events.fireEvent#http://localhost:3000/packages/leaflet.js?ad7b569067d1f68c7403ea1c89a172b4cfd68d85:37
o.Draggable<._onUp#http://localhost:3000/packages/leaflet.js?ad7b569067d1f68c7403ea1c89a172b4cfd68d85:39
o.DomEvent.addListener/s#http://localhost:3000/packages/leaflet.js?ad7b569067d1f68c7403ea1c89a172b4cfd68d85:39
Ok, nearly got it. The thing is that the error is created by the MarkerCluster plugin. When I exclude it, it works with the following approach:
var newMarker;
Happening.find({}).observe({
added: function(marker) {
markerInit(marker);
markers[newMarker.options._id] = newMarker;
return map.addLayer(newMarker)
},
changed: function(marker){
map.removeLayer(markers[marker._id]);
markerInit(marker);
markers[newMarker.options._id] = newMarker;
map.addLayer(markers[marker._id]);
}
});
markerInit() sets up the markers like my code before does. I'm still not sure how to get it with MarkerCluster working.
Related
I am creating a walkthrough for the interior of a building and am wondering how to draw a marker on a StreetViewPanorama. Maybe I'm missing something basic, but everything I have read indicates that you need a lat and lng to draw the marker.
Here is what I have:
var initPosPanoID, streetView;
function initPano() {
// Set StreetView provider.
var streetViewOptions = {
zoom: 0,
panoProvider: getCustomPanorama,
pano : "lobby",
addressControlOptions: {
position: google.maps.ControlPosition.BOTTOM_CENTER
}
};
// Create a StreetView object.
var streetViewDiv = document.getElementById('map');
streetView = new google.maps.StreetViewPanorama(streetViewDiv, streetViewOptions);
// Add links when it happens "links_change" event.
google.maps.event.addListener(streetView, "links_changed", createCustomLink);
google.maps.event.addListener(streetView, "pano_changed", function() {
var panoCell = document.getElementById('pano-cell');
panoCell.innerHTML = panorama.getPano();
});
}
function getCustomPanoramaTileUrl(panoID, zoom, tileX, tileY) {
// Return a pano image given the panoID.
return "images/he/" + panoID + '.jpg';
}
function getHeading(panoID) {
var heading;
switch(panoID) {
case "lobby":
heading = 0;
break;
case "secorner":
heading = 100;
break;
}
return heading;
}
function getCustomPanorama(panoID) {
// get a custom heading for the pano
var heading = getHeading(panoID);
var streetViewPanoramaData = {
links: [],
copyright: 'Imagery (c) HumanElement',
tiles: {
tileSize : new google.maps.Size(1024, 512),
worldSize : new google.maps.Size(1024, 512),
// The heading in degrees at the origin of the panorama
// tile set.
centerHeading : heading,
getTileUrl : getCustomPanoramaTileUrl
}
};
switch(panoID) {
case "lobby":
// Description of this point.
streetViewPanoramaData["location"] = {
pano: 'lobby',
description: "Human Element",
latLng: new google.maps.LatLng(42.282138, -83.751471)
};
return streetViewPanoramaData;
case "secorner":
streetViewPanoramaData["location"] = {
pano: 'secorner',
description: "SouthEast Corner",
latLng: new google.maps.LatLng(42.282078, -83.751413)
};
return streetViewPanoramaData;
}
}
function createCustomLink() {
var links = streetView.getLinks();
var panoID = streetView.getPano();
switch(panoID) {
case "lobby":
links.push({
description : "SouthEast Corner",
pano : "secorner",
heading : 280
});
break;
case "secorner":
links.push({
description : "HumanElement Lobby",
pano : "lobby",
heading : 90
});
break;
}
}
I would like to have different markers or touchpoints at different locations, but am not sure how to get them on there.
Trying to draw a standard marker does not work without the lat/lng.
I thought about trying to create it around a google.maps.Point thinking I might be able to use the x and y from my tiles, but couldn't seem to get that working.
The other options I see are related to google.maps.drawing.DrawingManager.
Does anyone have any advice on this?
Setting up the map this way helped: It creates the map and panorama separately and then sets the panorama into the map, rather than just having the panorama object by itself.
map = new google.maps.Map(document.getElementById('map'), {
center: position,
zoom: 10
});
// Create the panorama and set it into the map
panorama = new google.maps.StreetViewPanorama(
document.getElementById('map'), {
position: position,
pov: {
heading: 0,
pitch: -10
},
// Override the default panoId to outside the building to start
pano:'e_giRekRylYAAAQn7y2xAg'
});
map.setStreetView(panorama);
For placing markers on the map, I found an excellent plugin which creates points and then places elements on those points.
https://github.com/marmat/google-maps-api-addons/tree/master/panomarker
So far this is working very well. If you have an interior walkthrough, you should be able to toggle markers based on the PanoId. You can even add click listeners to open up dialogs.
In short, I want to do:
Meteor.publish('items', function(){
return Item.find({categoryId: Categories.find({active: true} });
});
The flag 'active' as part of 'Categories' changes regularly.
I also tried unsub/resub to the Items collection by leveraging reactivity on the Categories collections, and it works, unfortunately it re-triggers on ANY modification to the Categories collection, regardless if it affected the 'active' flag or not.
What are my options?
Nothing solved the issue of the items not being 'deleted' locally when the category is flagged as inactive on the server. Solution (ish) is to:
Client:
Categories.find({active: true}).observeChanges({
added: function(){
itemsHandle && itemsHandle.stop();
itemsHandle = Meteor.subscribe("items");
}
});
Server:
Meteor.publish('items', function(){
var category = Categories.findOne({active: true});
return category && Items.find({categoryId: Categories.findOne({active: true}._id);
});
I realize this isn't perfect (still uses client side code), but it works and its the cleanest I could think of. I hope it helps someone!
A possible solution is to create a dependency object, watch for all categories change, and trigger the dep change if the active flag was toggled. Something along these lines:
var activeCount = Categories.find({active: true}).count();
var activeDep = new Deps.Dependency();
Deps.autorun(function() {
var activeCountNow = Categories.find({active: true}).count();
if(activeCountNow !== activeCount) {
activeCount = activeCountNow;
activeDep.changed();
}
});
Meteor.publish('items', function(){
activeDep.depend();
return Item.find({categoryId: Categories.find({active: true} });
});
Note: I'm only verifying whether the number of active categories have changes so that I don't have to keep the active list in the memory. This may or may not be appropriate depending on how your app works.
Edit: Two-sided flavor mentioned in the comments:
Client:
var activeCount = Categories.find({active: true}).count();
var activeDep = new Deps.Dependency();
Deps.autorun(function() {
var activeCountNow = Categories.find({active: true}).count();
if(activeCountNow !== activeCount) {
activeCount = activeCountNow;
activeDep.changed();
}
});
Deps.autorun(function(){
activeDep.depend();
Meteor.subscribe('items', new Date().getTime());
});
Server:
Meteor.publish('items', function(timestamp) {
var t = timestamp;
return Item.find({categoryId: Categories.find({active: true} });
});
Meteor.startup(function() {
Categories.find().observe({
addedAt: function(doc) {
trigger();
},
changedAt: function(doc, oldDoc) {
if(doc.active != oldDoc.active) {
trigger();
}
},
removedAt: function(oldDoc) {
trigger();
}
});
});
Now, the trigger function should cause the publish to rerun. This time it's easy when it's on the client (change subscription param). I'm not sure how to do this on the server - perhaps run publish again.
I use the following publish to solve a similar issue. I think it is only the one line nesting of queries that limits the reactivity. Breaking one query out inside the publish function seems to avoid the issue.
//on server
Meteor.publish( "articles", function(){
var self= this;
var subscriptions = [];
var observer = Feeds.find({ subscribers: self.userId }, {_id: 1}).observeChanges({
added: function (id){
subscriptions.push(id);
},
removed: function (id){
subscriptions.splice( subscriptions.indexOf(id)) , 1);
}
});
self.onStop( function() {
observer.stop();
});
var visibleFields = {_id: 1, title: 1, source: 1, date: 1, summary: 1, link: 1};
return Articles.find({ feed_id: {$in: subscriptions} }, { sort: {date: -1}, limit: articlePubLimit, fields: visibleFields } );
});
//on client anywhere
Meteor.subscribe( "articles" );
Here is another SO example which gets the search criteria from the client through subscribe if you decide that is acceptable.
Update: Since the OP struggled to get this going I made a gist and launched a working version on meteor.com. If you just need the publish function it is as above.
i'm trying to create a chart using dojo. I choose a StackedColumns chart. I want to make it interactive. When a user clicks on a graph columns, an hyperlink should be invoked. But to create hyperlink'url, I need the series name. Is there any way to get Series Name when user clicks on a column? I've been searching for days but i didn't found any solution. Here is my code:
<div id="chartNode" style="width: 1024px; height: 768px;"></div>
<div id="legend"></div>
<script type="text/javascript">
require(['dojox/charting/Chart',
'dojox/charting/themes/PrimaryColors',
'dojox/charting/plot2d/StackedColumns',
'dojox/charting/plot2d/Grid',
'dojox/charting/widget/Legend',
'dojox/charting/action2d/Tooltip',
'dojox/charting/action2d/Magnify',
'dojox/charting/action2d/Highlight',
'dojo/store/Observable',
'dojo/store/Memory',
'dojox/charting/StoreSeries',
'dojox/charting/axis2d/Default',
'dojo/domReady!'], function(Chart, theme, StackedColumns,Grid,Legend,Tooltip,Magnify,Highlight,Observable,Memory,StoreSeries){
var myData = [{id:1,value:1,site:'1'},
{id:2,value:2,site:'1'},
{id:3,value:6,site:'1'},
{id:4,value:4,site:'1'},
{id:5,value:5,site:'1'},
{id:6,value:1,site:'2'},
{id:7,value:3,site:'2'},
{id:8,value:1,site:'2'},
{id:9,value:2,site:'2'},
{id:10,value:7,site:'2'}];
var myStore = new Observable(new Memory({
data: { identifier: 'id',
items: myData
}
}));
var serie_1 = new StoreSeries(myStore, { query: { site: 1 } }, 'value');
var serie_2 = new StoreSeries(myStore, { query: { site: 2 } }, 'value');
var myChart = new Chart('chartNode');
myChart.setTheme(theme);
myChart.addAxis('x', { fixLower: 'minor',
fixUpper: 'minor',
natural: true,
rotation: 90
});
myChart.addAxis('y', {vertical: true,fixLower: 'major',fixUpper: 'major',minorTicks: true,includeZero: true});
myChart.addPlot('myPlot', { type: 'StackedColumns', gap: 5, minBarSize: 8});
myChart.addSeries('Serie 1',serie_1,{ stroke: { color: 'red' }, fill: 'lightpink' });
myChart.addSeries('Serie 2',serie_2,{ stroke: { color: 'blue' }, fill: 'lightblue' });
var highlight = new Highlight(myChart, 'myPlot');
myChart.render();
var legend = new Legend({ chart: myChart }, 'legend');
myChart.connectToPlot('myPlot',function(evt) {
// React to click event
if(type == 'onclick') {
var msg = 'x:'+evt.x+';y:'+evt.y+';index: '+evt.index+';value: '+evt.run.data[evt.index];
alert(msg);
//How to get series informations when onclick event fires???
}
var tip = new Tooltip(myChart,'myPlot',{text:function(evt){return evt.run.data[evt.index];}});
});
});
</script>
I tried this in a stacked bar and works well:
chart.connectToPlot("default", function(evt) {var type = evt.type; if(type=="onclick") console.log(evt.run.name);})
evt.run.name provides the series name based on the column you clicked
Great! Thank you Noelia!
I have used it for a pie chart. You can get the index number of the slice with the parameter evt.index.
I use GMAP3 plugin to render driving direction. And would like to add a clear button so it can be clear but I haven't been able to find the right syntax in GMAP3. Here is the my js code, modified from the sample in gmap3.net. I have markers plotted already and latlng are retreived from plotted markers instead of from clicks position on the map.
function removePath() {
$(mapID).gmap3({
action: 'clear',
name: 'directionRenderer'
// tag: 'path' // works too with tag instead of name
});
function updatePath() {
$(mapID).gmap3({
action: 'getRoute',
options: {
origin: m1.getPosition(),
destination: m2.getPosition(),
travelMode: google.maps.DirectionsTravelMode.DRIVING
},
callback: function (results) {
if (!results) return;
$(mapID).gmap3({
action: 'setDirections',
directions:results,
});
}
});
};
function updateDirection(mm) { // Directions between m1 and m2
var mmID = $(mm).prop('id');
...
if (mmID == 'clearDirection') {
...
removePath();
return;
};
...
if (m1 && m2) { updatePath(); };
};
function initmap() {
$(mapID).gmap3(
{
action: 'init',
options: defaultMapOptions
},
// add direction renderer to configure options (else, automatically created with default options)
{ action: 'addDirectionsRenderer',
preserveViewport: true,
markerOptions: { visible: false },
options: {draggable:true},
tag: 'path'
},
// add a direction panel
{ action: 'setDirectionsPanel',
id: 'directions'
}
);
};
A is in place in HTML documents as directions panel. It has a a wrapper which is hidden when the route is cleared by using jquery css property change. The wrapper div's display property is changed back to 'block' whenever value is assigned to either m1 or m2.
<body>
...
<div id="direction_container" class="shadowSE">
....
<div id="directions"></div>
....
</div>
</body>
Its absolutely working fine.
$map.gmap3({ action: 'clear', name: 'directionRenderer' });
*Instructions-
If you later draw the route then you must write below code otherwise directions not display.
$map.gmap3({ action: 'addDirectionsRenderer', preserveViewport: true,
markerOptions: { visible: false} },
{ action: 'setDirectionsPanel', id: 'directions' });
Thanks...
Use this:
$(mapID).gmap3({action:"clear", name:"directionRenderer"});
The chosen answer above didn't work for me. I'm unsure if it's version related, but the solution I'm using is more simple:
$(your-selector).gmap3({clear: {}});
Afterwards, you can draw a new route without reconnecting the directions rendered with the map.
I'm quite new to sencha touch. The goal is to create an app which has a TabPanel containing four Tabs, one of them should be a map (the others are a NestedList and two Panels working like a charm). I've tried to make the map card like
NPApp.views.Mapcard = Ext.extend(Ext.Map, { ...
where I ended up with getting really strange results like some views are overlapping and no map is shown.
The second try was to creating a Panel, embed it into the TabPanel and add a map to the panel, where I get this error:
Uncaught TypeError: Cannot read property 'ROADMAP' of undefined;
sencha-touch-debug.js:24840
I've already tried to change the mapType to google.map.MapTypeID like mentioned in the Google Map API V3, no success there.
I just can't get the hang on it, hope you can give me some hints!
The App:
NPApp = new Ext.Application({
name: "NPApp",
title: "NextPuff",
icon: 'images/icon.png',
tabletStartupScreen: 'images/index_default.jpg',
phoneStartupScreen: 'images/index_default.jpg',
launch: function() {
this.views.viewport = new this.views.Viewport();
this.views.homecard = this.views.viewport.getComponent('navi');
}
});
The Viewport:
NPApp.views.Viewport = Ext.extend(Ext.TabPanel, {
fullscreen: true,
store: NPApp.npstore,
initComponent: function() {
Ext.apply(this, {
tabBar: {
dock: 'bottom',
layout: {
pack: 'center'
}
},
items: [
{ xtype: 'homecard', stretch: true},
{ xtype: 'searchcard', id: 'navi' },
{ xtype: 'mapcard' },
{ xtype: 'morecard' }
]
});
NPApp.views.Viewport.superclass.initComponent.apply(this, arguments);
}
});
The Mapcard:
NPApp.views.Mapcard = Ext.extend(Ext.Panel, {
title: "Map",
iconCls: "map",
initComponent: function() {
var npMap = new Ext.Map({
title: 'Map',
useCurrentLocation: true,
listeners: {
centerchange : function(comp, map){
// refreshMap(map);
}
},
mapOptions : {
mapTypeControl : false,
navigationControl : false,
streetViewControl : false,
backgroundColor: 'transparent',
disableDoubleClickZoom: true,
zoom: 17,
draggable: false,
keyboardShortcuts: false,
scrollwheel: false
}
});
Ext.apply(this, {
defaults: {
styleHtmlContent: true
},
items: [npMap]
});
NPApp.views.Homecard.superclass.initComponent.apply(this, arguments);
}
});
Ext.reg('mapcard', NPApp.views.Mapcard);
Sencha 1.1.0; Google JavaScript Maps API V3; Safari 5.1
I have a similar application running. Your tabpanel is perfect. All you need to alter is your map code.... Try this instead :
var map = new Ext.Map({
mapOptions : {
center : center,
zoom : 20,
mapTypeId : google.maps.MapTypeId.HYBRID,
navigationControl: true,
navigationControlOptions: {
style: google.maps.NavigationControlStyle.DEFAULT
}
},
listeners : {
maprender : function(comp, map){
var marker = new google.maps.Marker({
position: center,
//title : 'Sencha HQ',
map: map
});
setTimeout( function(){map.panTo (center);} , 1000);
}
},
geo:new Ext.util.GeoLocation({
autoUpdate:true,
maximumAge: 0,
timeout:2000,
listeners:{
locationupdate: function(geo) {
center = new google.maps.LatLng(geo.latitude, geo.longitude);
if (map.rendered)
map.update(center)
else
map.on('activate', map.onUpdate, map, {single: true, data: center});
},
locationerror: function ( geo,
bTimeout,
bPermissionDenied,
bLocationUnavailable,
message) {
if(bLocationUnavailable){
alert('Your Current Location is Unavailable on this device');
}
else{
alert('Error occurred.');
}
}
}
})
});
This creates the map object and sets the center to ur current location. Now you need to dock this object inside an Ext.extend(Ext.Panel({}) object. Ive tried directly creating the map object but it needs a panel to display on.
So you're panel code should go something like so:
NPApp.views.Mapcard = new Ext.extend(Ext.Panel({
iconCls : 'map',
title : 'Map',
layout: 'card',
ui: 'light',
items: [map],
listeners:{
}
});
)
It took me ages of going thru a dozen or more examples to make the current location work. This is a combination of several codes and a bunch of stuff in the Google API.
Lemme know if you have any more questions about Google Maps or directions.
Cheers :)
Sasha