I have a Rails template which uses a javascript_tag to get the variables #user.lat and #user.lng to the client for use in a function that generates a Google map.
The problem is that these variables are dynamic and can change often, but my javascript tag only knows the value of the variables at the time of page rendering. When I click around my app (triggering actual page loads) I can see that the values change, but the values in the javascript_tag aren't updated after the initial page load.
How can I keep the variables up-to-date on the client without refreshing the page or writing code to repeatedly query the server for these variables?
The javascript_tag approach you're taking doesn't support "watching" the variables for changes. In other words, your method of getting the data to the client is sort of "set it and forget it" and the client will never know if the value changes on the server without a page reload.
Another approach would be to use a pre-rolled solution for making dynamic server-side data onto the client, keeping an eye on them for changes, such as Gon. Specifically, you might want to take a look at the "watch" functionality provided by that gem.
Yet another approach might be to write up a function that makes an AJAX call to the server to request the latest value every so often. But then you'd just be hand-rolling a solution that does exactly the same thing as the previous suggestion.
'The javascript_tag approach you're taking doesn't support "watching" the variables for changes....'
Yes, true, even when I was clicking around my application and reloading the page, the values in the javascript_tag stayed the same. It was like once my js file loaded on start up of my application, the latitude and longitude values of #user stayed the same, even though they're supposed to change, and the map was always drawn with the same coordinates.
The 'Gon' gem didn't work for me either. Very little documentation, especially for a novice like me.
What succeeded:
On the page where I wanted the map to be generated, show.html.erb:
<!-- This is here, to generate the user's latitude and longitude dynamically -->
<!-- The data-lat and lng attributes are used in 'initialise_google_maps' function, in scripts.js' -->
<div id="user-position" class="hidden" data-lat="<%= #user.lat %>" data-lng="<%= #user.lng %>"></div>
And then in my scripts.js:
function initialize_google_maps() {
var user_longitude = $("#user-position").attr("data-lng");
var user_latitude = $("#user-position").attr("data-lat");
var currentlatlng = new google.maps.LatLng(user_latitude, user_longitude);
var zoom = 10;
var myOptions = {
zoom: zoom,
center: currentlatlng,
mapTypeId: google.maps.MapTypeId.ROADMAP, // ROADMAP, SATELLITE, HYBRID
streetViewControl: false
};
map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
var marker = new google.maps.Marker({map: map, position: currentlatlng, icon:{oppacity:0}});
var circle = new google.maps.Circle({
map: map,
fillOpacity: 0,
strokeWeight: 2,
strokeOpacity: 0.7,
radius: 10000,
});
circle.bindTo('center', marker, 'position');
}
Works a treat now.
Related
Basically, what I have is a geolocation data (longtitude and latitudes) of 300.000 locations. I have different attributes attached to the data and it is approx. 32MB. Reading it through js and putting markers on google maps is what I've tried, and It works OK when i put only 25 to 2500 markers on my map, I cant really put all of my locations at once. Eventually I want to be able to filter markers through the attributes etc. The locations are all at one city, so I might use my own map or something.
What I want to ask/learn is do you have any better solutions for this particular situation?
Here is my code.
function initJS() {
var promise = getData();
var locations1;
var locations;
promise.success( (data) => {
locations1 = parseData(data);
locations = locations1.filter(location => {
return location.DuyuruTipi == "16";
});
//initializing google map
map = new google.maps.Map(document.getElementById("map"), {
center: { lat: latitude, lng:longtitude},
zoom: zooming,
});
//creating a marker
const svgMarker = {
// path: "M10.453 14.016l6.563-6.609-1.406-1.406-5.156 5.203-2.063-2.109-1.406 1.406zM12 2.016q2.906 0 4.945 2.039t2.039 4.945q0 1.453-0.727 3.328t-1.758 3.516-2.039 3.070-1.711 2.273l-0.75 0.797q-0.281-0.328-0.75-0.867t-1.688-2.156-2.133-3.141-1.664-3.445-0.75-3.375q0-2.906 2.039-4.945t4.945-2.039z",
url: "./assets/marker.svg",
size: new google.maps.Size(20,20),
scaledSize: new google.maps.Size(20,20),
origin: new google.maps.Point(0, 0),
anchor: new google.maps.Point(10, 10),
};
for (var i = 0; i < locations.length; i++) { //locations.length
// init markers
var marker = new google.maps.Marker({
position: { lat: parseFloat(locations[i]["YCoor"]), lng: parseFloat(locations[i]["XCoor"])},
map: map,
label: locations[i]["DuyuruId"],
icon: svgMarker
});
console.log(locations[i]["DuyuruTipi"]);
marker.duyurutipi = locations[i]["DuyuruTipi"];
marker.kazatipi = locations[i]["KazaTipi"];
marker.vsegid = locations[i]["vSegID"];
markers.push(marker);
}
});
Displaying 300000 points directly on the map is not the correct approach and will not perform well, especially as more datasets get added to your map.
In addition, sending 32MB of data or more to the browser is bad form, even for a web map application. If you try out e.g. Google Maps with the network panel open, you'll see that you'll barely go over a few MB even after quite some time using it.
There are a couple approaches that web mappers take to counter this:
Use a service such as Geoserver or Mapserver to split up the data into "chunks" based on what is in the the map clients (openlayers, in your case, according to your answer) viewport. This is the best choice if you could potentially have lots of layers or basemaps in future, but is a lot of work to setup and configure.
Write your own implementation of the above in your back-end. For points, this is relatively simple. This is the best choice for something quick with just a couple of points layers.
In both cases, you'll need to configure your points layer in OpenLayers to use the "bbox" strategy, which will tell it to call your API url whenever the viewport changes enough for more features to be loaded. You will also need to set the minimum resolution for your layer so that it doesn't load too many features all at once when zoomed out.
Lastly, with Openlayers, you'll want to use a VectorImageLayer with a VectorSource for this layer, which will improve performance a lot while allowing you to query and edit your point data.
The above should help to improve your mapping performance.
Well, I went with the OpenLayers API, I think it is harder to implement stuff from docs but they have example applications for every feature. You might want to try that, way better performance if your only need is to put some markers and visualize data.
As you can see in the below living atlas layer, there are multiple layers inside (state,county,tract etc..)
But when I add this link, it will display only the first layer.(State). Then, when I zoom in the map, that layer disappears.
I want to display all the 4 layers of that feature service.(state,county, tract, blockgroups)
How do I achieve this?
.ts
const genderLayer = new FeatureLayer({
url: "https://services2.arcgis.com/FiaPA4ga0iQKduv3/ArcGIS/rest/services/US_Census_Age_Gender/FeatureServer",
});
const layersToCreateMyPopupTemplate = [ageLayer,genderLayer];
const map = new Map({
basemap: 'topo-vector',
layers: layersToCreateMyPopupTemplate
});
const view = new MapView({
container,
map: map,
zoom: 3,
center: [-97.63, 38.34],
});
.html
<!-- Map Div -->
<div #mapViewNode></div>
The issue you are having is that,
you are trying to use FeatureLayer with the whole map service, that means in that service you have 4 FeatureLayers, one for each layer of the service,
state https://services2.arcgis.com/FiaPA4ga0iQKduv3/ArcGIS/rest/services/US_Census_Age_Gender/FeatureServer/0
county https://services2.arcgis.com/FiaPA4ga0iQKduv3/ArcGIS/rest/services/US_Census_Age_Gender/FeatureServer/1
tract https://services2.arcgis.com/FiaPA4ga0iQKduv3/ArcGIS/rest/services/US_Census_Age_Gender/FeatureServer/2
block group https://services2.arcgis.com/FiaPA4ga0iQKduv3/ArcGIS/rest/services/US_Census_Age_Gender/FeatureServer/3
it disappear because the state layer, the first one (number 0), it is only visible until Max. Scale: 20000001
That type of service it is made on purpose in that way, to make it efficient (easy to understand) and with good performance. That is, it shows what it need to show at each scale.
Usually is better to display using MapImageLayer or similar and then query the using the feature service. Sadly, in this case it does not offer that kind of service (MapServer), so you will have to creat four FeatureLayer, one for each.
I'm using react to load streetview into a component I get this :
As we can see - the map component loads fine, and the streetview component seems to load fine, except that the image behind it (the actual street view) isn't loaded.
The error reported is :
**
Uncaught TypeError: Cannot read property 'toString' of undefined in
maps.googleapis.com/imagery_viewer.js:299
**
When I run similar code in straight-up HTML, this works ... sometimes ... often I get no returned image
I've isolated the code into a special function triggered with a large setTimeout() call so that the document load time is not to blame. Indeed, using watches and breakbpoints, we can see that the DIVs are loaded and accessible at run time.
handleLoad: ->
options = {
addressControl: true
fullscreenControl: true
panControl: true
pov: this.props.image.pov
visible: true
zoomControl: true
}
if this.props.image.pano
options.pano = this.props.image.pano
else if this.props.image.position
options.position = new google.maps.LatLng this.props.image.position[0], this.props.image.position[1]
if this.props.image.position
pos = new google.maps.LatLng this.props.image.position[0], this.props.image.position[1]
else
pos = {lat: this.props.entryLatitude, lng: this.props.entryLongitude};
options.key ='XXXX - GOOGLE MAP API KEY GOES HERE - XXXX';
this.map = new google.maps.Map(this.mapDiv, { center: pos, zoom: 14, visible:true } , streetViewControl: false);
this.panorama = new google.maps.StreetViewPanorama this.panoramaDiv, options;
this.map.setStreetView(this.panorama);
Any suggestions what could be causing this problem ?
This known black street view panorama issue can be cache or cors related. I suggest you first try ruling both of them out. E.g. see https://support.google.com/maps/thread/6166296?msgid=8836199
It may also be due to a third-party library bug or conflict. E.g. if you're using mootools or prototype.js check out these threads:
Mootools breaks Google maps streetview - black screen
https://issuetracker.google.com/issues/139252493
Another potential reason could be that your lat/lngs are far away from the roads where the street view imagery is available. Have a look at this great answer to related question Google map JS API "StreetViewPanorama" display black screen for some address
Let us know if any of the linked solutions work for you; otherwise please provide a codesandbox or stackblitz that reproduces the issue and I'll be happy to investigate further.
here is my code in html to generate marker and infowindow(with ruby on rails)
var marker=[]
function initMap() {
var latLng1 = new google.maps.LatLng(1.352083, 103.819836);
var myOptions = {
zoom: 12,
center: latLng1,
mapTypeId: google.maps.MapTypeId.ROADMAP
}
map = new google.maps.Map(document.getElementById('map_canvas'), myOptions);
for(i=0;i<gon.astatic.length;i++){
var latLng = new google.maps.LatLng(gon.astatic[i][1], gon.astatic[i][2]);
if(i<2){
marker[i] = new MarkerWithLabel({position: latLng, map: map,icon:"/assets/green_MarkerV.png" ,labelClass: "labels",labelContent: gon.astatic[i][3]});}
else
{
marker[i] = new MarkerWithLabel({position: latLng, map: map,icon:"/assets/green_MarkerN.png" ,labelClass: "labels",labelContent: gon.astatic[i][3]});
}
var iw =new google.maps.InfoWindow({content: 'HI' });
google.maps.event.addListener(marker[i],"mouseover",function(e){iw.open(map,marker[i]);})
}
this gon is just some 'stupid' method I use to pass data from ruby on rails controller to javascript.
for all marker,the infowindow all appear at corner.
But for my another map(which have only one marker with infowindow)it works fine.
What might be my problem?why this infowindow appear in wrong position?Instead of just above the marker?
EDIT:
After half day's trouble shoot,I feel the problem is at
google.maps.event.addListener(marker[i],"mouseover",function(e){iw.open(map,marker[i]);})
when the listener calls back,the value inside marker is i ,which is not a actual number,so the marker display at a corner.I feel the problem is can't pass variable into addListener,can only put in actual number.How to solve this?
Each instance of the function declared inside the for loop shares the same closure containing the value i, and so all of your addListener calls are essentially calling iw.open(map, undefined) since i will be off the end of the array at the end of the iteration.
See JavaScript closure inside loops – simple practical example for sample solutions to this problem, and How do JavaScript closures work for more information about closures in JavaScript in general.
The problem is with your MarkerWithLabel library. Infowindow take position from marker. Try use this link http://google-maps-utility-library-v3.googlecode.com/svn/tags/markerwithlabel/1.1.8/docs/examples.html . It has all the things that you want to implement. It it's not work then you can also set position for infowindow with setPosition() function just pass latlng that you used to create marker and you are done.
i dont recommend using new gem just to pass data from ruby to js...you can do this simply by many ways...your code seems good but i cannot say how gon is handling your js script.Please take a look at this similar question where i have implemented the same dynamic map with dynamic markers and infowindows.This code is working great
see here
I'm trying to create map (using the Google Maps JavaScript API V3) which consists of several partially-transparent layers. By default, these layers should all be overlaid on top of one another to form a complete map, but the user should be able to turn any combination of them on or off (while preserving order) to create whatever view they prefer.
So far, I've had a great deal of luck getting this working for a single layer using map.mapTypes, but when adding all the layers via map.overlayMapTypes, I've hit a couple of snags:
The map doesn't seem to get fully initialized if map.setMapTypeId() is not called (no controls appear and the map is not correctly centered) and it cannot be called with an overlay.
It isn't clear how to toggle the visibility of an overlay without directly modifying the map.overlayMapTypes array, which complicates keeping them correctly ordered. I'd much prefer something analogous to the Traffic/Transit/Photos/etc. control available within Google Maps itself.
Here's the initialize function I'm working with. I'd post a link, but the map imagery isn't publicly available:
function initialize() {
map = new google.maps.Map(document.getElementById("map_canvas"), {
zoom: 0,
center: center
});
/* if these lines are uncommented, the single layer displays perfectly */
//map.mapTypes.set("Layer 3", layers[3]);
//map.setMapTypeId("Layer 3");
//return;
var dummy = new google.maps.ImageMapType({
name: "Dummy",
minZoom: 0,
maxZoom: 6,
tileSize: new google.maps.Size(256, 256),
getTileUrl: function() {return null; }
});
map.mapTypes.set("Dummy", dummy);
map.setMapTypeId("Dummy");
// layers is an array of ImageMapTypes
for (var i = 0; i < layers.length; i++) {
map.overlayMapTypes.push(layers[i]);
}
}
As you can see, I've tried creating a "dummy" maptype (which always returns null for tile URLs) to serve as the base map. While this does cause the controls to display, it still doesn't center correctly.
What's the best way to create a map which consists only of toggleable overlays?
Update: Turns out the dummy maptype works perfectly well if you also remember to set a projection. That's one problem solved, at least. :-)
I use ImageMapType, but I don't add it to mapTypes. I just add it to overlayMapTypes and when I need to remove it I use setAt to set the entry in overlayMapTypes to null.
You will need to add individual controls to the UI that toggle the individual layers.