Showing posts with label leaflet. Show all posts
Showing posts with label leaflet. Show all posts

Friday, June 8, 2018

How to add polylines from one location to others separately using leaflet in shiny?

1 comment

I'm trying to add polylines from one specific location to many others in shiny R using addPolylines from leaflet. But instead of linking from one location to the others, I am only able to link them all together in a sequence. The best example of what I'm trying to achieve is seen here in the cricket wagon wheel diagram: .

observe({   long.path <- c(-73.993438700, (locations$Long[1:9]))   lat.path <- c(40.750545000, (locations$Lat[1:9]))   proxy <- leafletProxy("map", data = locations)   if (input$paths) {      proxy %>% addPolylines(lng = long.path, lat = lat.path, weight = 3, fillOpacity = 0.5,                         layerId = ~locations, color = "red")   } }) 

It is in a reactive expression as I want them to be activated by a checkbox.

I'd really appreciate any help with this!

3 Answers

Answers 1

Note

I'm aware the OP asked for a leaflet answer. But this question piqued my interest to seek an alternative solution


Example

Other answers indicate you need to create an individual line for each from/to coordinate pair. In this case you need separate lines for each of the lines going from the center to the outer points.

Here's an example using googleway (my package, which interfaces Google Maps API), and works on data.frames and data.tables (as per this example), rather than spatial (sp or sf) objects.

The trick is in the encodeCoordinates function, which encodes coordinates (lines) into a Google Polyline

library(data.table) library(googleway) library(googlePolylines) ## gets installed when you install googleway  center <- c(144.983546, -37.820077)  setDT(df_hits)  ## data given at the end of the post  ## generate a 'hit' id df_hits[, hit := .I]  ## generate a random score for each hit df_hits[, score := sample(c(1:4,6), size = .N, replace = T)]  df_hits[     , polyline := encodeCoordinates(c(lon, center[1]), c(lat, center[2]))     , by = hit ]  set_key("GOOGLE_MAP_KEY") ## you need an API key to load the map  google_map() %>%     add_polylines(         data = df_hits         , polyline = "polyline"         , stroke_colour = "score"         , stroke_weight = "score"         , palette = viridisLite::plasma     ) 

enter image description here


The dplyr equivalent would be

df_hits %>%     mutate(hit = row_number(), score = sample(c(1:4,6), size = n(), replace = T)) %>%     group_by(hit, score) %>%     mutate(         polyline = encodeCoordinates(c(lon, center[1]), c(lat, center[2]))     ) 

Data

df_hits <- structure(list(lon = c(144.982933659011, 144.983487725258,  144.982804912978, 144.982869285995, 144.982686895782, 144.983239430839,  144.983293075019, 144.983529109412, 144.98375441497, 144.984103102141,  144.984376687461, 144.984183568412, 144.984344500953, 144.984097737723,  144.984065551215, 144.984339136535, 144.984001178199, 144.984124559814,  144.984280127936, 144.983990449363, 144.984253305846, 144.983030218536,  144.982896108085, 144.984022635871, 144.983786601478, 144.983668584281,  144.983673948699, 144.983577389175, 144.983416456634, 144.983577389175,  144.983282346183, 144.983244795257, 144.98315360015, 144.982896108085,  144.982686895782, 144.982617158347, 144.982761997634, 144.982740539962,  144.982837099486, 144.984033364707, 144.984494704658, 144.984146017486,  144.984205026084), lat = c(-37.8202049841516, -37.8201201023877,  -37.8199253045246, -37.8197812267274, -37.8197727515541, -37.8195269711051,  -37.8197600387923, -37.8193828925304, -37.8196964749506, -37.8196583366193,  -37.8195820598976, -37.8198956414717, -37.8200651444706, -37.8203575362288,  -37.820196509027, -37.8201032825917, -37.8200948074554, -37.8199253045246,  -37.8197897018997, -37.8196668118057, -37.8200566693299, -37.8203829615443,  -37.8204295746001, -37.8205355132537, -37.8194761198756, -37.8194040805737,  -37.819569347103, -37.8197007125418, -37.8196752869912, -37.8195015454947,  -37.8194930702893, -37.8196286734591, -37.8197558012046, -37.8198066522414,  -37.8198151274109, -37.8199549675656, -37.8199253045246, -37.8196964749506,  -37.8195862974953, -37.8205143255351, -37.8200270063298, -37.8197430884399,  -37.8195354463066)), row.names = c(NA, -43L), class = "data.frame") 

Answers 2

Here is a possible approach based on the mapview package. Simply create SpatialLines connecting your start point with each of the end points (stored in locations), bind them together and display the data using mapview.

library(mapview) library(raster)  ## start point root <- matrix(c(-73.993438700, 40.750545000), ncol = 2) colnames(root) <- c("Long", "Lat")  ## end points locations <- data.frame(Long = (-78):(-70), Lat = c(40:44, 43:40))  ## create and append spatial lines lst <- lapply(1:nrow(locations), function(i) {   SpatialLines(list(Lines(list(Line(rbind(root, locations[i, ]))), ID = i)),                 proj4string = CRS("+init=epsg:4326")) })  sln <- do.call("bind", lst)  ## display data mapview(sln) 

lines

Just don't get confused by the Line-to-SpatialLines procedure (see ?Line, ?SpatialLines).

Answers 3

I know this was asked a year ago but I had the same question and figured out how to do it in leaflet.

You are first going to have to adjust your dataframe because addPolyline just connects all the coordinates in a sequence. It seems that you know your starting location and want it to branch out to 9 separate locations. I am going to start with your ending locations. Since you have not provided it, I will make a dataframe with 4 separate ending locations for the purpose of this demonstration.

dest_df <- data.frame (lat = c(41.82, 46.88, 41.48, 39.14),                    lon = c(-88.32, -124.10, -88.33, -114.90)                   ) 

Next, I am going to create a data frame with the central location of the same size (4 in this example) of the destination locations. I will use your original coordinates. I will explain why I'm doing this soon

orig_df <- data.frame (lat = c(rep.int(40.75, nrow(dest_df))),                    long = c(rep.int(-73.99,nrow(dest_df)))                   ) 

The reason why I am doing this is because the addPolylines feature will connect all the coordinates in a sequence. The way to get around this in order to create the image you described is by starting at the starting point, then going to destination point, and then back to the starting point, and then to the next destination point. In order to create the dataframe to do this, we will have to interlace the two dataframes by placing in rows as such:

starting point - destination point 1 - starting point - destination point 2 - and so forth...

The way I will do is create a key for both data frames. For the origin dataframe, I will start at 1, and increment by 2 (e.g., 1 3 5 7). For the destination dataframe, I will start at 2 and increment by 2 (e.g., 2, 4, 6, 8). I will then combine the 2 dataframes using a UNION all. I will then sort by my sequence to make every other row the starting point. I am going to use sqldf for this because that is what I'm comfortable with. There may be a more efficient way.

orig_df$sequence <- c(sequence = seq(1, length.out = nrow(orig_df), by=2)) dest_df$sequence <- c(sequence = seq(2, length.out = nrow(orig_df), by=2))  library("sqldf") q <- " SELECT * FROM orig_df UNION ALL SELECT * FROM dest_df ORDER BY sequence " poly_df <- sqldf(q) 

The new dataframe looks like this (notice how the origin locations are interwoven between the destination):

SS of data

And finally, you can make your map:

library("leaflet") leaflet() %>%   addTiles() %>%    addPolylines(     data = poly_df,     lng = ~lon,      lat = ~lat,     weight = 3,     opacity = 3   )  

And finally it should look like this:

SS of Leaflet Map

I hope this helps anyone who is looking to do something like this in the future

Read More

Monday, April 23, 2018

Layers disappear when panning in DukeScript Leaflet4j

Leave a Comment

I am test driving Leaflet4j inside of JavaFX combo.

When I run the l4jfxdemo project and then drag the map the circle and polygon layers disappear. They do not disappear when zooming. I added a DragEndListener to enumerate the layers and they are still there but don't show on the map. When you zoom in or out you can see them appear for a brief moment.

I recompiled Leaflet4j with different versions of Leaflet but it made no difference. I also tried various map options as well as redrawing of the layers after panning but still no joy.

What could be causing this behaviour?

0 Answers

Read More

Saturday, April 14, 2018

Layers disappear when panning in DukeScript Leaflet4j

Leave a Comment

I am test driving Leaflet4j inside of JavaFX combo.

When I run the l4jfxdemo project and then drag the map the circle and polygon layers disappear. They do not disappear when zooming. I added a DragEndListener to enumerate the layers and they are still there but don't show on the map. When you zoom in or out you can see them appear for a brief moment.

I recompiled Leaflet4j with different versions of Leaflet but it made no difference. I also tried various map options as well as redrawing of the layers after panning but still no joy.

What could be causing this behaviour?

0 Answers

Read More

Wednesday, April 11, 2018

How to make svg <image> element show up in safari/ios

Leave a Comment

My scenario: I'm displaying a leaflet map. A map has several tiles, each tile might contain one or more icons. Here is how a tile might look like:

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" pointer-events="none" width="256" height="256" viewBox="0 0 256 256" class="leaflet-tile leaflet-tile-loaded" style="width: 256px; height: 256px; transform: translate3d(455px, -4px, 0px); opacity: 1;">     <g></g>     <image x="213.9375" y="252.875" width="19px" height="19px" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAOxAAADsQBlSsOGwAAA/1JREFUWIXll21olWUYx3/Py9nZc8523uZyW+Z8gVXK3Dw2iJx0ilGGKVOnOIp0RJjRhyDWSAgJI4R9mA2hpohGVKizpQZGG4MWM5ZntnK1mQutdQz3ctzOzs778zx9sJ1tsJdzju5DdH26r+u+r+v/4+J57hf4v5swMcjLy9sVDAarBUFQF1JQ13XJZDId8ng8Z6YBOByOKzU1NWudTudC6uN2u6mtrXV7vd4SAHnKXMzpdFJWVpZYJV0j0N1I+EYbtk11CJIhoTRVVRFFMd5lea7Fs1n4Zhu+1veIDvx6NyCI2DfXp1IqOYDoYC++1oOEb3w7LR785QtkWz6ZG95cGADVf5uxtloCV0+Drs24Zqy9Dsm2FFPhjvsHoEfG8Xd8iP+HBvRocN5ioxerkSx5GPPXJwwgziJNoOtTbjeUMtZ+OCFxAF2LcafpFWLD1+8RQFMJ9pxDGx9MuFA8NeRj+PRLCefOCDB2qR7LM+8jLypIGgBAHe3H21iFHgulBhAbusbIuX04yj9CylicEkTk7y7unH991o92TgBdU4kO9DDacgB7xUmEtIyUIEK/fY2v9WDyABPU4ZvfEXAfx7HtKIKY0p6F//Ixxq+cTBJAmAwHuhuJ/NmB9bnalAAARpsPEOprSRzAsqEa0ZgZ98cufQBqNKWdDkA0ZqAFvYkDyNkPY992fNoBM/LNftJyizEVVSauLEiYnbvJqjyFYDAnDgBgzH8C26Y64ie2FsP75auYi17AuMI1r7ZxWSmLXmzC8MAqQr1fYch+JDkAAGVVORbX23Ffj4wzfLYK69PvYMgpnDFHtuVj33oMc8legj3nkawPkbbcRfiP9uQBADIefw3zuqq4r40P4m3ai33LESTrknhcSMvA4tqPbetRord+RPMPYC6uJPLXZWIDPSiPbk4NAMBa9i7pBRvjfmy4j5GLb+GoOIGo2DGt2UX27gsIkoHw9WaUwu3oWoxAdxPpBRsRTXYCP51KHQBBxL7lCGkPPhYPRfo78LfXs3jf96SvfIpA12fIOcUYcosIdH2ObF2CcfmTBK6eQZAVzCUv3wMAIMjpOCpOIDtWxmPB3guoPg8A6au3E+5rnmy9x01s6BqZ698gveDZWa9sCQMAiIqdrJ2fIJqzJ8GMVlTfLcJ9LdNar6wux7xuD6Jim7tmMgAAkm0pWTs+Rki7+1/r0XHknLWTrbfnY3HVTOvUfQUAMOSswVHeAKKMrqqEf29GCwxhcdVgXFbKlNv+vJbaCQMYV7iwP38YSbFidu5BsuSmVCcOoOu67Ha7UdVkHkYK9Hf9O/45oYzOzk40TZMm/KlPs52hUKh66qNhIUzTNElRlEMej+fsQur8d+wfq09oFkuvTdQAAAAASUVORK5CYII=" class="" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAOxAAADsQBlSsOGwAAA/1JREFUWIXll21olWUYx3/Py9nZc8523uZyW+Z8gVXK3Dw2iJx0ilGGKVOnOIp0RJjRhyDWSAgJI4R9mA2hpohGVKizpQZGG4MWM5ZntnK1mQutdQz3ctzOzs778zx9sJ1tsJdzju5DdH26r+u+r+v/4+J57hf4v5swMcjLy9sVDAarBUFQF1JQ13XJZDId8ng8Z6YBOByOKzU1NWudTudC6uN2u6mtrXV7vd4SAHnKXMzpdFJWVpZYJV0j0N1I+EYbtk11CJIhoTRVVRFFMd5lea7Fs1n4Zhu+1veIDvx6NyCI2DfXp1IqOYDoYC++1oOEb3w7LR785QtkWz6ZG95cGADVf5uxtloCV0+Drs24Zqy9Dsm2FFPhjvsHoEfG8Xd8iP+HBvRocN5ioxerkSx5GPPXJwwgziJNoOtTbjeUMtZ+OCFxAF2LcafpFWLD1+8RQFMJ9pxDGx9MuFA8NeRj+PRLCefOCDB2qR7LM+8jLypIGgBAHe3H21iFHgulBhAbusbIuX04yj9CylicEkTk7y7unH991o92TgBdU4kO9DDacgB7xUmEtIyUIEK/fY2v9WDyABPU4ZvfEXAfx7HtKIKY0p6F//Ixxq+cTBJAmAwHuhuJ/NmB9bnalAAARpsPEOprSRzAsqEa0ZgZ98cufQBqNKWdDkA0ZqAFvYkDyNkPY992fNoBM/LNftJyizEVVSauLEiYnbvJqjyFYDAnDgBgzH8C26Y64ie2FsP75auYi17AuMI1r7ZxWSmLXmzC8MAqQr1fYch+JDkAAGVVORbX23Ffj4wzfLYK69PvYMgpnDFHtuVj33oMc8legj3nkawPkbbcRfiP9uQBADIefw3zuqq4r40P4m3ai33LESTrknhcSMvA4tqPbetRord+RPMPYC6uJPLXZWIDPSiPbk4NAMBa9i7pBRvjfmy4j5GLb+GoOIGo2DGt2UX27gsIkoHw9WaUwu3oWoxAdxPpBRsRTXYCP51KHQBBxL7lCGkPPhYPRfo78LfXs3jf96SvfIpA12fIOcUYcosIdH2ObF2CcfmTBK6eQZAVzCUv3wMAIMjpOCpOIDtWxmPB3guoPg8A6au3E+5rnmy9x01s6BqZ698gveDZWa9sCQMAiIqdrJ2fIJqzJ8GMVlTfLcJ9LdNar6wux7xuD6Jim7tmMgAAkm0pWTs+Rki7+1/r0XHknLWTrbfnY3HVTOvUfQUAMOSswVHeAKKMrqqEf29GCwxhcdVgXFbKlNv+vJbaCQMYV7iwP38YSbFidu5BsuSmVCcOoOu67Ha7UdVkHkYK9Hf9O/45oYzOzk40TZMm/KlPs52hUKh66qNhIUzTNElRlEMej+fsQur8d+wfq09oFkuvTdQAAAAASUVORK5CYII=" style="pointer-events: auto;"></image> </svg> 

Result in Chrome,Firefox (inspected via devtools):

enter image description here

In Safari however, the icons are not rendered. The element is there, but the picture is missing. Screenshot from browserstack for safari, ios6. The highlighted blue box is the element (inspected via devtools again), showing that the element is in position, with the correct dimensions, but no image is showing:

enter image description here

What I have tried:

  • Using absolute and relative url paths for the image resource instead of inline base64. Makes no difference. The image is also hosted on the same domain, no cross-domain issues apply.
  • Using various combinations of xlink:href and href (just xlink:href, just href, etc).
    • Modifying the image/svg tag. Added the appropriate namespaces and the xlink:href tag (default library only used href), as per this suggestion.

What I haven't tried:

  • Completely replacing the svg mechanism of leaflet with another (say...canvas). Much of the application relies on the svg renderer, so I'd rather not go there.

Are there any other suggestions I could try except replacing the svg renderer?

Minimum reproducible example: https://jsfiddle.net/tocxvxy3/3/

1 Answers

Answers 1

You could try to directly put the png file name, intead of embbeding it in svg, as in leaflet examples http://leafletjs.com/examples/custom-icons/ , maybe the ios support is better this way?

var map = L.map('map').setView([51.5, -0.09], 13);    	L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {  		attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'  	}).addTo(map);    	var LeafIcon = L.Icon.extend({  		options: {  			shadowUrl: 'leaf-shadow.png',  			iconSize:     [50, 50],  			shadowSize:   [50, 64],  			iconAnchor:   [22, 94],  			shadowAnchor: [4, 62],  			popupAnchor:  [-3, -76]  		}  	});    	var greenIcon = new LeafIcon({iconUrl: 'https://memegenerator.net/img/images/50x50/7452314.jpg', iconRetinaUrl: 'https://static01.nyt.com/images/2012/09/14/blogs/Fils-Aime/Fils-Aime-thumbLarge.jpg'});    	L.marker([51.5, -0.09], {icon: greenIcon}).addTo(map);
html, body {    height: 100%;    margin: 0;  }  #map {    width: 600px;    height: 400px;  }
<link href="https://unpkg.com/leaflet@1.3.1/dist/leaflet.css" rel="stylesheet"/>  <script src="https://unpkg.com/leaflet@1.3.1/dist/leaflet.js"></script>  <div id='map'></div>

There's also an iconRetinaUrl option if you want to try

var greenIcon = new LeafIcon({   iconUrl: 'https://memegenerator.net/img/images/50x50/7452314.jpg',   iconRetinaUrl: 'https://static01.nyt.com/images/2012/09/14/blogs/Fils-Aime/Fils-Aime-thumbLarge.jpg' }); 
Read More

Saturday, October 21, 2017

How can I force npm to resolve a dependency's dependency to a different package?

Leave a Comment

TL;DR: How can I change one of my package's dependency's dependencies to a different package? For instance, I want to change Package A's dependency Package B to be Package C, but only for Package A (i.e. I don't want to change Package A's dependencies upstream).


I'm writing a plugin for Leaflet. Leaflet is available as an NPM package (and my plugin will be, too, when I'm finished). There's also another plugin my plugin extends, Esri-Leaflet, that has Leaflet as a dependency.

My plugin uses Mocha/Chai/Sinon as a test framework for my code. I run these tests with an NPM script both during development and as a part of CI.

When I run tests that depend upon Leaflet I have a number of errors because Leaflet, unfortunately, depends upon a number of globals not available in a headless node environment (such as window). Fortunately, there is an alternative package that was suggested to me called leaflet-headless that shims over those problems (it's actually pretty interesting to see if you're curious).

Is there a way to, just for my package, tell NPM to use leaflet-headless intead of Leaflet when resolving Esri-Leaflet's dependencies? That is, I either want to remove the downstream Leaflet dependency (because my project already depends upon leaflet-headless) or change it to be leaflet-headless.

I've looked at npm shrinkwrap, but it seems like it can only specify version numbers, not change actual package dependencies.

If there is no functionality with NPM to do what I'm asking, can you recommend an alternative solution? I'm already exploring fixing the reliance upon globals in Leaflet upstream in order to eliminate the need for leaflet-headless.

1 Answers

Answers 1

This may or may not work depending on your version of Node, but I was able to accomplish something similar on Node 8 with a same-named package.

If you need to make the package the same name, you can fork the project, and rename the fork to be the same (in this case leaflet-headless -> Leaflet).

After you have your fork, write the following in your package.json

dependencies: {     "Leaflet": "username/Leaflet" } 

If you have a package-lock.json, you should see Esri-Leaflet point to your github, and not the original Leaflet project as a dependency.

Read More

Saturday, July 30, 2016

Angular2 - Fire event on Leaflet-Event

Leave a Comment

I am trying to impement Leaflet with Angular 2 TS for my Ionic 2 app. I want to emit my pinClicked-event when a Leaflet-pin was clicked. How to do this? In Angular1 $scope.$apply was the solution...

private refreshMarkers() {     L.marker([40.731253, -73.996139])       .addTo(this.map)       .on('click', function() { alert('JA'); } );   }    private pinWasClicked() {     this.pinClicked.emit('');   } 

0 Answers

Read More

Saturday, July 9, 2016

How to precisely place a div element on a map using leaflet js?

Leave a Comment

I have tried this couple of ways but have not been able to get it working. I want to place clocks at the top of the map within multiple timezones. I have a the javascript to create the clock and I place the clock in a div element.

Here is what I tried:

  1. Create a Point with 0,0 coordinates.
  2. From this point get the latitude value for the top of the map using containerPointToLatLng.
  3. Create LatLng using the above lat and long for the timezone.
  4. Converted this LatLng to Point and then positioning the div element with the x,y from this point.

I execute the logic both when the page is first rendered and then on body resize. However, if I change the size of the browser window, the clock does not position correctly.

Any suggestions?

2 Answers

Answers 1

To answer OP's precise issue, i.e. how to re-position the clock when the browser window is resized (hence map container dimensions may have changed), one should probably just re-compute the clock position on map's "resize" event.

However, it is not clear whether OP placed the clock as a child of the map container, or somewhere else on the page DOM tree.

It is probably much easier to place it as a child of the map, so that its position is always relative to the map container.

What OP originally asked?

If I understand correctly the original desired result, the OP would like to overlay a clock (or whatever information) on top of a particular geographical position (Toronto city in that case [UTC -5], according to comments).

However, the information container should not lay at a basemap fixed position, i.e. not at a precise geographic coordinated point (like a marker or a popup would), but at the top of the map container, similarly to a Control (hence iH8's original answer).

Except that it should not be totally fixed within the map container, but horizontally move with the city (or whatever specified geographical coordinates). Hence OP's comment to iH8's answer.

Therefore it sounds like something similar to that site, except with an interactive (navigate-able) map and the "UTC-5" header replaced by a clock (or whatever information, hence an HTML container should do it) and horizontally following Toronto.

Put differently, the clock should sit at a particular vertical line, i.e. longitude / meridian.

Unfortunately, even 2 years and a half after the question is posted, there is still no Leaflet plugin that provides such functionality (at least within Leaflet plugins page).

Extending the use case to highly zoomed-in map…

That being said, and given the fact that the user may be able to zoom highly into the city (OP did not specify the maximum zoom level), it might not be a very good user experience having that clock horizontally follow a precise longitude: for example, it could track Toronto centroid / city hall / whatever particular place, and when user is zoomed-in at another city district, the clock is no longer visible, whereas he/she is still viewing a part of Toronto city…

To extend that use case, the clock should very probably be visible in whatever area it applies, i.e. as soon as the map view port intersects the associated time zone.

Extending the use case to highly zoomed-out map…

Another point not detailed by OP, is what to do when places of different time zones are visible in the map view port? In the above mentioned site, we have one header per visible time zone, which seems the most complete information we can get.

But since Leaflet allows to zoom out down to level 0, where the entire world (i.e. essentially 24 time zones / actually 39 according to Wikipedia, not including potential effect of Daylight Saving Time - DST) is represented with a 256 pixels width, there is little room to fit all these clocks, if each one must be vertically aligned with its associated time zone.

For now let's assume we do not care if clocks overlap.

Even more custom case…

But OP may have wished to display the clock only for particular places, not for the entire world. OP did not even say that clocks would be different (we could have clocks for cities in the same time zone, even though it could be more interesting to have these clocks sit next to their city - even on par with their latitude, so that it is easier to spot which city the clock is associated to, like in the case of 2 cities on the same meridian; but in that case, a marker with L.divIcon would be enough).

Hence a custom case would be not to consider official time zones, but developer's specified areas.

So we forget about the latitude and try to align a clock vertically above the area, as long as it intersects the map view port.

Describing a generic solution

Therefore it sounds like a generic solution would be to enable the application developer to specify an array of HTML Elements, each one associated with a range of longitudes (could also be an area / polygon).

The time zones use case would then be a particular case where the specified areas are simply those from the time zones.

Then, each Element should be visible if and only if its associated area intersects the view port (therefore we introduce a possibility to hide it when the latitude range is out of view).

As for positioning, let's choose:

  • By the top of the map container (similar to a Control), as mentioned by OP.
  • Horizontally centered within the intersection of the view port and of the associated area.

HTML:

<div id="map"></div>  <div id="clockToronto" class="clock leaflet-control">Clock here for Toronto</div> <div id="clockBurlington" class="clock leaflet-control">Clock here for Burlington</div> 

CSS:

.clock {   width: 150px;   text-align: center;   position: absolute;   border: 1px solid black; }  #clockToronto {   background-color: yellow; }  #clockBurlington {   background-color: orange; } 

JavaScript:

var map = L.map("map").setView([43.7, -79.4], 10);  // Application specific. var clockTorontoElement = L.DomUtil.get("clockToronto"),     clockBurlingtonElement = L.DomUtil.get("clockBurlington"),     zones = [       {         element: clockTorontoElement, // Using the HTML Element for now.         width: parseInt(window.getComputedStyle(clockTorontoElement, null).getPropertyValue("width"), 10),         area: L.latLngBounds([43.58, -79.64], [43.86, -79.10]) // Using L.latLngBounds for now.       },       {         element: clockBurlingtonElement, // Using the HTML Element for now.         width: parseInt(window.getComputedStyle(clockBurlingtonElement, null).getPropertyValue("width"), 10),         area: L.latLngBounds([43.28, -79.96], [43.48, -79.71]) // Using L.latLngBounds for now.       }     ];  // Initialization var controlsContainer = map._container.getElementsByClassName("leaflet-control-container")[0],     firstCorner = controlsContainer.firstChild,     mapContainerWidth;  map.on("resize", setMapContainerWidth); setMapContainerWidth();  // Applying the zones. for (var i = 0; i < zones.length; i += 1) {   setZone(zones[i]); }  function setZone(zoneData) {   // Visualize the area.   L.rectangle(zoneData.area).addTo(map);   console.log("width: " + zoneData.width);    controlsContainer.insertBefore(zoneData.element, firstCorner);   map.on("move resize", function () {     updateZone(zoneData);   });   updateZone(zoneData); }  function updateZone(zoneData) {   var mapBounds = map.getBounds(),       zoneArea = zoneData.area,       style = zoneData.element.style;    if (mapBounds.intersects(zoneArea)) {     style.display = "block";      var hcenterLng = getIntersectionHorizontalCenter(mapBounds, zoneArea),         hcenter = isNaN(hcenterLng) ? 0 : map.latLngToContainerPoint([0, hcenterLng]).x;      // Update Element position.     // Could be refined to keep the entire Element visible, rather than cropping it.     style.left = (hcenter - (zoneData.width / 2)) + "px";   } else {     style.display = "none";   } }  function getIntersectionHorizontalCenter(bounds1, bounds2) {   var west1 = bounds1.getWest(),       west2 = bounds2.getWest(),       westIn = west1 < west2 ? west2 : west1,       east1 = bounds1.getEast(),       east2 = bounds2.getEast(),       eastIn = east1 < east2 ? east1 : east2;    return (westIn + eastIn) / 2; }  function setMapContainerWidth() {   mapContainerWidth = map.getSize().x; } 

enter image description here

Live demo: http://plnkr.co/edit/V2pvcva5S9OZ2N7LlI8r?p=preview

Answers 2

Usually one would use L.Control to create a custom control which you can then add to the control layer. If you do so, leaflet will take care of positioning when resizing the map. Take a look at the reference for L.Control: http://leafletjs.com/reference.html#control

There is an example of a custom control in the reference: http://leafletjs.com/reference.html#icontrol If you would like to see more examples you could check out one of the many custom control plugins to see how they implemented L.Control: http://leafletjs.com/plugins.html (under Controls and interaction)

The only drawback of L.Control is that you can't position a control vertically or horizontally centered. You may only use topleft, topright, bottomleft & bottomright.

Read More

Sunday, April 10, 2016

Render leaflet map after flexbox calculates height

Leave a Comment

I am trying to draw a map in a flex child that takes up remaining space.

I have a couple headers on top of the main content, and one of the banners is sometimes hidden/removed, so I am using flexbox so that the header can go away without affecting the other elements position. I.E this does not work well when I use absolute positions.

My problem is that it seems like leaflet is calculating the maps viewport before flexbox has calculated height for the map div.

Example:

Here is a screenshot of leaflet thinking the maps height is smaller than it should be.

enter image description here

If I check the elements height it is correct. And furthermore if I put a timer in and force leaflet to invalidate the map size, the map re-renders at the correct height.

HTML:

<body>   <div class="header">       Header   </div>    <div class="banner">       Banner   </div>    <main class="main">     <div class="nav">       <strong>Navigation</strong>     </div>      <div id="map" class="map-content"></div>      </main>       <script src="script.js"></script>   </body> 

CSS:

html, body {   height: 100%;   width: 100%;   display: flex;   flex-direction: column; }  .header {     height: 50px;     background-color: grey;     color: white; }  .banner {      height: 50px;     background-color: lightgrey;     color: white; }  .main {     display: flex;     flex: 1 1 auto;     flex-direction: row; }  .map-content {    flex: 1 1 auto; }  .nav {     background-color: #2A3D4A;     color: white;     width: 200px; } 

EDIT:

I have reproduced the problem in a plunk, hooray! However I still don't know exactly what is happening. The problem only occurs when I include ngAnimate in my application. Doesn't matter if I use ngAnimate or not.

Here is the plunk that is broken. On initial page load the map is fine. Click the about link, then go back to the map. Notice that when going back, only about half the map loads. I log the height to the console. When you nav away from the map and come back its always about half the size.

What is angular animate doing to cause the element to be half to size for a split second? Is this a bug in angular?

http://plnkr.co/edit/dyBH1Szo3lIEWajKqXWj?p=preview

3 Answers

Answers 1

It looks like the leaflet map gets the height when created and then you need to call .invalidateSize() if the height changes. In the docs for leaflet it states Make sure the map container has a defined height, for example by setting it in CSS (see http://leafletjs.com/examples/quick-start.html). Since you are not setting the height explicitly but instead have css rule position: absolute; top:0; bottom: 0; left: 0; right: 0; it is being a bit dodgy.

I believe the problem is that when you include ngAnimate it has an effect on elements with transition properties, and somehow the correct height doesn't get seen by leaflet in time. I think you will need to use a workaround e.g. detecting a change in height and calling .invalidateSize()

Try adding this to your directive:

link: function(scope, element) {   scope.$watch(function() { return element.parent().height(); }, function (val) {     console.log('height changed to: ' + val);     scope.map.invalidateSize();   }); }, scope: true, 

Make your controller set $scope.map = L.map('map',{crs: L.CRS.EPSG4326}).setView([0,0], 4); so the leaflet map object is on the scope, and remove the css rules you have on .map-content

Answers 2

I had a similar problem. What was happening is that as directives were loading, a post link function calculated the clientHeight of a div element. The div fills remaining space with flex. But it always was off by some amount. I tracked it down to angular calling the post link before all template partials had been retrieved from the web server. Another directive was still waiting on its urlTemplate, and once it came in, that directive caused another div to change height.

If I just put a timeout of enough time, it worked around the problem. Also, if I broadcast an event from the late div and waited on that before calculating the height, this also works around the problem.

I could not find an easy way to tell when all templates had been loaded, and all directives had done their thing manipulating the DOM. Directive priorities did absolutely nothing to change the order of things.

Look at sequence of events in the browser and see if it isn't a partial template arriving late that is throwing off the timing enough so that calculations are not exactly performed when you asked them to be performed.

Answers 3

Edited plunkr : http://plnkr.co/edit/pIg9HKwkl0Bfhk4N3wQX?p=preview

try to "invalide" the map like this :

app.directive('leaflet', ['$document','$timeout', function($document,$timeout) {    function controller() {     console.log('in leaflet directive controller');      var element = $document.find('#map');     console.log('main container height: ' + element[0].offsetHeight);      var map = L.map('map',{crs: L.CRS.EPSG4326}).setView([0,0], 4);     L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);      // the timeout wait the view to render     $timeout(function () {        // this rebuild the map        map.invalidateSize();     }, 0);   }    return {     template: '<div id="map" class="map-content"></div>',     controller: controller,     replace: true   }  }]); 
Read More