Mapping Antarctic Polar Stereographic data in ipyleaflet - python

I am trying to plot Antarctic Sea-Ice EPSG:3031 data using ipyleaflet like so:
from ipyleaflet import Map, WMSLayer, basemaps
wms = WMSLayer(
url='http://geos.polarview.aq/geoserver/wms',
layers='polarview:iceedgeS15',
format='image/png',
transparent=True,
attribution='Polarview'
)
m = Map(basemap=basemaps.NASAGIBS.BlueMarble3031, center=(-90, 0), zoom=1, crs=projections.EPSG3031)
m.add_layer(wms)
m
The photos in the following links illustrate my issues clearly:
The data and the basemap do not align
If I omit the basemap and projection information it looks reasonable in terms of alignment but doesn't have the perspective and projection I desire. I have been also been using Leafmap to add local geotiffs and run into similar issues.
I have read through a few relevant PRs and checked out xarray-leaflet but haven't had any luck. This sea-ice concentration data is an example of a non-wms data source that I encounter the same problem with which could be helpful for testing purposes.

Great question.
You'll need to add coordinate reference system (CRS) information to your WMSLayer. See the custom projections notebook within the ipyleaflet examples. For spatial projections that are not Web Mercator (EPSG:3857), you'll likely need to define the bounds of your imagery.
from ipyleaflet import Map, WMSLayer, basemaps
# create map with NASA Blue Marble as background
m2 = Map(basemap=basemaps.NASAGIBS.BlueMarble3031,
center=(-90, 0), zoom=1, crs=projections.EPSG3031)
# projection for Polarview sea ice edge
POLAR3031 = dict(
name='EPSG:3031',
custom=True,
proj4def="""+proj=stere +lat_0=-90 +lat_ts=-71 +lon_0=0 +k=1
+x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs""",
bounds=[[-2822131.5,-3057369.25],[3744213.75,3822194.25]]
)
wms = WMSLayer(
url='http://geos.polarview.aq/geoserver/wms',
layers='polarview:iceedgeS15',
format='image/png',
transparent=True,
attribution='Polarview',
crs=POLAR3031
)
m2.add(wms)
# show map
m2

Related

ipyleaflet layer not scaled/positioned correctly

I'm trying to overlay an esri bathymetry map on my south polar stereographic map using the 'url' function in ipyleaflet. Doing this results with {z}/{y}/{x} in the url inserts the layer, but at a smaller size in the right-hand corner of the basemap. I tried to give the tilelayer the same crs, but that did not change the output. My code is as such:
from ipyleaflet import (
Map,
basemaps,
TileLayer,
basemap_to_tiles,
projections)
from ipywidgets import (Layout,
widgets as w
tile_layer = TileLayer(url='https://tiles.arcgis.com/tiles/C8EMgrsFcRFL6LrL/arcgis/rest/services/Antarctic_Basemap/MapServer/tile/{z}/{y}/{x}')
spsLayout = Layout(width='800px', height='800px')
m = Map(center=(-90, 0),
zoom=0,
layout=spsLayout,
basemap= basemaps.NASAGIBS.BlueMarble3031,
crs=projections.EPSG3031)
m.add_layer(tile_layer)
m
Here's where I got the url for the tiles In the bottom right. Thanks for your help!
I think the issue here is that this bathymetry base map use a different tilegrid(metatiles 3x3) from the one used with ipyleaflet(4x4). Unfortunately I don't think there is currently a way for Leaflet(hence ipyleaflet) to understand multiple tilegrids in the same map. A workaround would be to use ArcGIS in your notebook instead of ipyleaflet but that would require a paid subscription.
If you don't mind defining your own projection and resolutions you can use the ArcGIS layers and avoid completely thedefault polar projection and basemaps from ipyleaflet. i.e.
from ipyleaflet import (
Map,
basemaps,
TileLayer,
basemap_to_tiles,
projections)
ESRI_3031 = dict(
name='EPSG:3031',
custom=True,
proj4def="""+proj=stere +lat_0=-90 +lat_ts=-71 +lon_0=0 +k=1
+x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs""",
origin=[-3.06361E7, 3.0636099999999993E7],
resolutions=[
67733.46880027094,
33866.73440013547,
16933.367200067736,
8466.683600033868,
4233.341800016934,
2116.670900008467,
1058.3354500042335,
529.1677250021168,
264.5838625010584,
],
bounds=[
[-4524583.19363305,-4524449.487765655],
[4524449.4877656475,4524583.193633042]
]
)
tile_layer = TileLayer(url='https://tiles.arcgis.com/tiles/C8EMgrsFcRFL6LrL/arcgis/rest/services/Antarctic_Basemap/MapServer/tile/{z}/{y}/{x}')
spsLayout = Layout(width='800px', height='800px')
m = Map(center=(-90, 0),
zoom=0,
layout=spsLayout,
basemap= tile_layer,
crs=ESRI_3031)
m

How can you use RD coordinates instead of gps coordinates to plot traffic routes in a folium plot?

In want to show traffic routes on a folium map. These routes are in the Netherlands and the coordinates are in RD (EPSG:28992). I tried to map the routes using the following code:
my_map = folium.Map(location=(52.2130,5.2794), tiles='cartodbpositron', zoom_start=7, control_scale=True)
origin = [rap.rd_to_wgs(lon, lat) for lon, lat in zip(ontwaterd['dpsRDcod_x'],ontwaterd['dpsRDcod_y'])]
dest = [rap.rd_to_wgs(lon, lat) for lon, lat in zip(ontwaterd['dps2RDcod_X'],ontwaterd['dps2RDcod_Y'])]
coords = list(zip(origin, dest))
def line_objects_for_airport(zip_coords, airport_name, color_):
new_route = folium.FeatureGroup(name = airport_name)
for coord in zip_coords:
new_route = new_route.add_child(folium.PolyLine(locations=[coord[0], coord[1]], weight=.5, color=color_))
return new_route
route = line_objects_for_airport(coords, 'Test', color_ = 'green')
my_map.add_child(route)
folium.LayerControl().add_to(my_map)
my_map.save("test1.html")
webbrowser.open("test1.html")
But this won't work and this results in a random red line:
However it does work whenever I transform the RD coordinates to GPS coordinates:
from osgeo.osr import SpatialReference, CoordinateTransformation
epsg28992 = SpatialReference()
epsg4326 = SpatialReference()
epsg28992.ImportFromEPSG(28992)
epsg4326.ImportFromEPSG(4326)
latlon2rd = CoordinateTransformation(epsg4326, epsg28992)
rd2latlon = CoordinateTransformation(epsg28992, epsg4326)
def rd_to_wgs(lon, lat):
gps = rd2latlon.TransformPoint(lon, lat)
return gps[0], gps[1]
origin = [rd_to_wgs(lon, lat) for lon, lat in zip(ontwaterd['dpsRDcod_x'],ontwaterd['dpsRDcod_y'])]
dest = [rd_to_wgs(lon, lat) for lon, lat in zip(ontwaterd['dps2RDcod_X'],ontwaterd['dps2RDcod_Y'])]
coords = list(zip(origin, dest))
This looks good but I rather plot the lines using the RD coordinates. Because then it will be more precise, since this coordinate system is established especially for the Netherlands.
Anyone who knows how to adjust the above code so it works with the original RD coordinates?
Anything would help!
See this question on GIS stack exchange: https://gis.stackexchange.com/questions/198695/leaflet-changing-base-map-crs. It references the Leaflet WMS docs. See especially the "Notes to GIS users of WMS services".
The issue is that the base map itself is in a specific CRS. Web mapping platforms are designed to render geographic data on top of tiled images extremely quickly. Because of this, leaflet does not support re-projecting raster data or image tiles at all, and only supports 3 CRS options for tilesets: EPSG:3857 (web mercator), EPSG:3395 (world mercator), and EPSG:4326 (WGS84).
So unfortunately, the answer is that while you can use geopandas and folium to reproject your data and visualize it using leaflet using the tileset's CRS, you can't folium/leaflet to reproject map tiles into your CRS :/

Plotting Pandas Dataframe data using Folium GeoJson

I am trying to plot the amount of times a satellite goes over a certain location using Python and a heatmap. I easily generate the satellite data, but I am having issues with displaying it in a nice manner. I am trying to follow this example, as I can use the style function to lower the opacity. I am having some issues replicating this though as it seems that the GeoJson version they were using no longer accepts the same inputs. This is the dataframe I am using:
print(df.head())
latitude longitude countSp geometry
0 -57.9 151.1 1.0 POLYGON ((151.05 -57.95, 151.15 -57.95, 151.15...
1 -57.9 151.2 2.0 POLYGON ((151.15 -57.95, 151.25 -57.95, 151.25...
2 -57.8 151.2 1.0 POLYGON ((151.15 -57.84999999999999, 151.25 -5...
3 -57.8 151.3 3.0 POLYGON ((151.25 -57.84999999999999, 151.35 -5...
4 -57.8 151.4 2.0 POLYGON ((151.35 -57.84999999999999, 151.45 -5...
I then call folium through:
hmap = folium.Map(location=[42.5, -80], zoom_start=7, )
colormap_dept = branca.colormap.StepColormap(
colors=['#00ae53', '#86dc76', '#daf8aa',
'#ffe6a4', '#ff9a61', '#ee0028'],
vmin=0,
vmax=max_amt,
index=[0, 2, 4, 6, 8, 10, 12])
style_func = lambda x: {
'fillColor': colormap_dept(x['countSp']),
'color': '',
'weight': 0.0001,
'fillOpacity': 0.1
}
folium.GeoJson(
df,
style_function=style_func,
).add_to(hmap)
This is the error I get when I run my code:
ValueError: Cannot render objects with any missing geometries: latitude longitude countSp geometry
I know that I can use the HeatMap plugin from folium in order to get most of this done, but I have found a couple of issues with doing that. First is that I cannot easily generate a legend (though I have been able to work around this). Second is that it is way too opaque, and I am not finding any ways of reducing that. I have tried playing around with the radius, and blur parameters for HeatMap without much change. I think that the fillOpacity of the style_func above is a much better way of making my data translucent.
By the way, I generate the polygon in my df by the following command. So in my dataframe all I need folium to know about is the geometry and countSp (which is the number of times a satellite goes over a certain area - ~10kmx10km square).
df['geometry'] = df.apply(lambda row: Polygon([(row.longitude-0.05, row.latitude-0.05),
(row.longitude+0.05, row.latitude-0.05),
(row.longitude+0.05, row.latitude+0.05),
(row.longitude-0.05, row.latitude+0.05)]), axis=1)
Is there a good way of going about this issue?
Once again, they were looking for a way to express the purpose in a heat map, so I used Plotly's data on airline arrivals and departures to visualize it.
The number of flights to and from the U.S. mainland only was used for the data.
Excluded IATA codes['LIH','HNL','STT','STX','SJU','OGG','KOA']
Draw a straight line on the map from the latitude and longitude of the departure airport to the latitude and longitude of the arrival airport.
Draw a heat map with data on the number of arrivals and departures by airport.
Since we cannot use a discrete colormap, we will create a linear colormap and add it.
Embed the heatmap as a layer named Traffic
import pandas as pd
df_airports = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/2011_february_us_airport_traffic.csv')
df_airports.sort_values('cnt', ascending=False)
df_air = df_airports[['lat','long','cnt']]
df_flight_paths = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/2011_february_aa_flight_paths.csv')
df_flight_paths = df_flight_paths[~df_flight_paths['airport1'].isin(['HNL','STT','SJU','OGG','KOA'])]
df_flight_paths = df_flight_paths[~df_flight_paths['airport2'].isin(['LIH','HNL','STT','STX','SJU'])]
df_flight_paths = df_flight_paths[['start_lat', 'start_lon', 'end_lat', 'end_lon', 'cnt']]
import folium
from folium.plugins import HeatMap
import branca.colormap as cm
from collections import defaultdict
steps=10
colormap = cm.linear.YlGnBu_09.scale(0, 1).to_step(steps)
gradient_map=defaultdict(dict)
for i in range(steps):
gradient_map[1/steps*i] = colormap.rgb_hex_str(1/steps*i)
m = folium.Map(location=[32.500, -97.500], zoom_start=4, tiles="cartodbpositron")
data = []
for idx,row in df_flight_paths.iterrows():
folium.PolyLine([[row.start_lat, row.start_lon], [row.end_lat, row.end_lon]], weight=2, color="red", opacity=0.4
).add_to(m)
HeatMap(
df_air.values,
gradient=gradient_map,
name='Traffic',
mini_opacity=0.1,
radius=15,
blur=5
).add_to(m)
folium.LayerControl().add_to(m)
colormap.add_to(m)
m

Polygon overlay in leaflet map

I am creating an interactive map using ipyleaflet using the following code:
from ipyleaflet import Map, Polygon
polygon = Polygon(
locations=[[(38.844185,-4.804621),(39.241299,-1.899833),(40.74308,-2.205491),(40.34742,-5.17429),(38.844185,-4.804621)],[(39.365192,-1.941078),(40.867912,-1.567062),(41.276688,-4.670904),(39.775406,-4.976737),(39.365192,-1.941078)],[(39.706161,-1.849863),(41.207623,-1.465817),(41.617561,-4.594476),(40.117233,-4.908839),(39.706161,-1.849863)],[(39.702591,-5.033657),(40.101254,-2.077048),(41.602196,-2.389729),(41.204681,-5.413605),(39.702591,-5.033657)]],
color="green",
fill_opacity= 0.5,
fill_color="green"
)
m = Map(center=(38.5531, -4.6914), zoom=6)
m.add_layer(polygon);
m
The ouptut looks like this:
I am wondering how can I make the intersection of the polygons not be fully transparent. Looking at the attributes https://ipyleaflet.readthedocs.io/en/latest/api_reference/polygon.html in the documentation, I don't see any option?
An example of the desired output can be seen in the image below:
You've got bad results because ipyleaflet subtracts ovellaped polygons (you can see it in second example in the documentation link you posted, "Polygon with hole")
You need to add each polygon separately, I changed your code a bit, now it creates and applies polygons in loop:
from ipyleaflet import Map, Polygon
poligons = [[(38.844185,-4.804621),(39.241299,-1.899833),(40.74308,-2.205491),(40.34742,-5.17429),(38.844185,-4.804621)],
[(39.365192,-1.941078),(40.867912,-1.567062),(41.276688,-4.670904),(39.775406,-4.976737),(39.365192,-1.941078)],
[(39.706161,-1.849863),(41.207623,-1.465817),(41.617561,-4.594476),(40.117233,-4.908839),(39.706161,-1.849863)],
[(39.702591,-5.033657),(40.101254,-2.077048),(41.602196,-2.389729),(41.204681,-5.413605),(39.702591,-5.033657)]]
m = Map(center=(38.5531, -4.6914), zoom=6)
for poly in poligons:
polygon = Polygon(
locations= poly,
color="green",
fill_opacity= 0.5,
fill_color="green"
)
m.add_layer(polygon);
m
Result:

Folium PolyLine not showing up on map - folium 0.7.0 with python 3.6 (anaconda) in Jupyter notebook

I have a folium map of a neighborhood in New York City generated using the following code:
m = folium.Map(location=[40.7035, -73.990],
zoom_start=16.5,
tiles='cartodbpositron')
I then try to add lines connecting points on the map using folium.PolyLine(), but even though I see them listed when I call m._children, they don't show up on the map.
Here's the code to create the lines, where G is a networkx graph:
for x, y in G.edges():
points = [nx.get_node_attributes(G, 'loc')[x], nx.get_node_attributes(G, 'loc')[y]]
egde = folium.PolyLine(locations=points, weight=5, color='red')
edge.add_to(m)
A sample point:
[(-73.986635, 40.703988), (-73.988683, 40.702674)]
Output of m.children (first few lines):
OrderedDict([('cartodbpositron',
<folium.raster_layers.TileLayer at 0x12279feb8>),
('poly_line_ae5785771a2148c5a8559cb0085b10a4',
<folium.vector_layers.PolyLine at 0x122892128>),
('poly_line_ee73b495559940d484064e8c8492eda5',
<folium.vector_layers.PolyLine at 0x1229734a8>),
('poly_line_415a7ed70a2a425e876c8a6711408a6a', ...
Any idea what I might be doing wrong?
This is kind of odd that folium polyline expects coordinates in [latitude, longitude] format, whereas in general accepted format is [longitude, latitude].
Let's take an example:
I'm assuming you are using OSRM to get geometries.
OSRM geometries returns coordinates in the form of [[longitude,latitude],...].
If you'll directly use them with folium, it'll not show the polyline.
Reverse the geometry coordinates using following function:
def reverse_lon_lat(x):
a = [[p[1], p[0]] for p in x]
return a
and draw the polylines you intend to.

Categories

Resources