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.
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.
Recently I asked about referencing the data of an existing GeoJSON Leaflet object. My Leaflet map consists of data coming in a stream to my GeoJSON Leaflet object. User inputs can change a filter for the GeoJSON data, so to make the filter apply to both the existing and new data I am keeping track of my data in an array called myFeatures. Whenever the filters change or an item in myFeatures changes, I do the following:
myGeoJson.clearLayers();
myGeoJson.addData(myFeatures);
This is working to make my map update according to the newly updated feature data or the changes in the filter.
I am applying pop-ups to the GeoJSON object when I initialize my GeoJSON object:
var myGeoJson = L.geoJson(myFeatures, {
style: function(feature) {
...
},
pointToLayer: function(feature, latlng) {
return L.circleMarker(latlng, geojsonMarkerOptions);
},
filter: function(feature, layer) {
...
},
onEachFeature: function(feature, layer) {
if (feature.properties && feature.properties.popupContent) {
layer.bindPopup(feature.properties.popupContent);
}
}
});
When I click on an individual feature, the pop-up appears. However, the pop-up dismisses pretty quickly, thanks to clearLayers and addData being called. :(
Is there some kind of way to stop the pop-up dismissing in this situation?
Or - better question - is there a way to modifying existing data in a GeoJSON object or remove some (not all) data from a GeoJSON object?
To provide some context, my GeoJSON shows circle markers for each feature. The circle markers are colored based on a property of the feature. The property can actually change over time, so the marker's styling needs to be updated. A marker also times out after a while and needs to be removed from the map, but the other markers need to stay on the map.
There are for sure better ways to do that, but if you don't want to modify your code architecture too much, you could just create your popups in a specific layer, which you won't clear when you add your new data.
To give you an idea (markers play below the role of myGeoJson in your example):
var popup_id = {};
var popup_layer = new L.layerGroup();
var markers = new L.layerGroup();
$.each(testData, function(index, p) {
var marker = L.marker(L.latLng(p.lat, p.lon));
markers.addLayer(marker);
popup = new L.popup({offset: new L.Point(0, -30)});
popup.setLatLng(L.latLng(p.lat, p.lon));
popup.setContent(p.text);
popup_id[p.id] = popup;
marker.on('click', function() {
popup_id[p.id].openPopup();
popup_layer.addLayer(popup_id[p.id]);
markers.clearLayers();
})
});
popup_layer.addTo(map);
markers.addTo(map);
You also keep track of all your popups in a dictionary popup_id.
Since you haven't provided us with a JSfiddle it is a bit difficult to find the perfect answer for your case, but I hope that the popup layer (also here in my fiddle) gives you a good direction.
I'm developing a web mapping site (using google maps api 3.0 and javascript) that combines wms layers and ground overlays (uploaded rasters) that are displayed on top of google maps. The software is working well except that I'm having problems controlling the draw order of the layers. I would like to have the wms layer (the NRCS soils layer) displayed on top of the custom raster images, with the google maps as the base layer. Currently, the wms layer displays as expected on top of the google maps layer, but is covered by the raster layer. The question is: does the google maps api allow control of the order that layers are displayed (in my case the vector layer on top of raster layer on top of google maps)? I have tried setting the zindex of the display order, but that has not worked (but I could easily be missing something).
WMS layers are also rasters, not vectors -- I assume you are using an ImageMapType along with your GroundOverlay?
Leaving that aside, there is currently no way to control the layer ordering once they have been added to the map.
As a hack (untested) you may wish to add the layers in the order you wish them to be drawn, with some timeout between... I think this may work (again, untested).
(optional).
how i order it.
first i create layer in object like this.
layer = {};
layer.nrcs_soils= new google.maps.ImageMapType({
getTileUrl: function (coord, zoom) {
return getTileWmsUrl(coord, zoom, "nrcs_soils");
},
tileSize: new google.maps.Size(256, 256),
opacity: 1,
name:'nrcs_soils',
alt:{
layer_name:'nrcs_soils'
,order : 0
},
isPng: true
});
after that i create simple function add layer to map.
add_layer = function(layer_name){
//-- get order from object layer
var order_layer =layer[layer_name].alt.order;
map.overlayMapTypes.insertAt(order_layer ,layer[layer_name]);
}
just call this function for add and order layer from your setting on your object layer.
add_layer("nrcs_soils");
According to the OpenLayers documentation, the constructor, OpenLayers.Map(), allows for an additional property extent which is "The initial extent of the map" (see here).
However, I cannot get it to have any effect. I know I can set an initial extent by calling .zoomToExtent() after constructing the map. But I would like to use this extent property because I set a zoomend event in the eventListeners property but don't want it to trigger with an initial call to .zoomToExtent(). Does anyone have a clue how to use this extent property?
This is the code that isn't working
map = new OpenLayers.Map('map',{
extent: bounds,
layers: [osmLayer,vectorLayer],
projection: "EPSG:900913",
eventListeners: {
zoomend: function() {
//..zoomend event listener code
}
}
});
In the above example:
bounds is a valid OpenLayers.Bounds object
osmLayer and vectorLayer are valid OpenLayers.Layer objects of which osmLayer is the base layer.
What happens with above code is that the map is completely zoomed out (in fact you can't actually see anything) and any attempts to pan results in errors being thrown. To get to map into a correct state the user has to zoom in and then panning works again and you can see the map.
I'm afraid I'm not sure off-handedly how to make the code you listed work, however the following alternative has worked for me:
map = new OpenLayers.Map('map',{projection: new OpenLayers.Projection("EPSG:4326")});
//ADD A BUNCH OF LAYERS AND SUCH HERE
//Set map center to this location at this zoom level
map.setCenter(new OpenLayers.LonLat(-93.9196,45.7326),5);
As a last resort, you could put a line at the beginning of your zoom function:
if(!map_ready) return;
And then set the variable map_ready to true at the appropriate time.
I ran into the same problem.
The "extent" option mentioned in the documentation is there probably by mistake. It does not exist. I checked the source code and could not see any code handling such option. I contacted the author and he already created a pull request for the removal of this option from the documentation.