Skip to content

Commit 3e3c64f

Browse files
authored
Added support for non standard projections for Map and TileLayers (#598)
* Added support for non standard projections for Map and TileLayers * Added eslint to js, improved formatting, implemented no_wrap on js tile layer
1 parent 252e78e commit 3e3c64f

File tree

18 files changed

+7123
-162
lines changed

18 files changed

+7123
-162
lines changed

CHANGELOG.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
## UNRELEASED
2+
3+
Improvements:
4+
5+
* add custom map and wms projections #70
6+
* add changelog
7+
* add notebook to show how to use map projections
8+
9+
## v0.12.6
10+
11+
Improvements:
12+
13+
* Use leaflet-defaulticon-compatibility for icon image bundling #552
14+
* Refactor GeoJSON layer #573
15+
* Add means to save to HTML #574
16+
* Improve docs #575
17+
* Make the xarray dependency optional #561
18+
19+
Fixes:
20+
* Fix in the Vector tile layer #568
21+
* Fix issue with Phosphor dependency #563
22+
* Bug fix with respect to the GeoJSON layer #572
23+
24+
25+
## v0.12.4
26+
27+
Improvements:
28+
29+
* The package now ships the JupyterLab extension automatically. So jupyter labextension install jupyter-leaflet should not be needed anymore #510
30+
* Add support for int data in Choropleth #539
31+
* Add style_callback to GeoJSON/Choropleth/GeoData layers #518
32+
* Rename positional argument in handle_draw callback #530
33+
* Add VectorTilesLayer #544
34+
35+
## v0.12.3
36+
37+
* JupyterLab 2 support #509
38+
* Sync Path's fill_color attribute with color attribute #505
39+
* Documentation improvements #497 #506
40+
41+
## v0.12.2
42+
43+
Fixes:
44+
45+
* Popup creation #489
46+
* DrawControl creation #493
47+
48+
Improvements:
49+
50+
* Smoother URL changes on GridLayers #485
51+
* ScaleControl #492
52+
* Documentation improvements #484 #485
53+
* WMSLayer: Listen for dynamic changes on parameters #494
54+
55+

docs/source/api_reference/map.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ center (0.0, 0.0) Initial geograph
5959
zoom 12 Initial map zoom level
6060
max_zoom 18
6161
min_zoom 1
62-
crs 'EPSG3857' Coordinate reference system, which can be 'Earth', 'EPSG3395', 'EPSG3857', 'EPSG4326', 'Base', or 'Simple'
62+
crs projections.EPSG3857 Coordinate reference system, which can be 'Earth', 'EPSG3395', 'EPSG3857', 'EPSG4326', 'Base', 'Simple' or you can define your own projection. (See CustomProjections notebook)
6363
dragging True Whether the map be draggable with mouse/touch or not
6464
touch_zoom True Whether the map can be zoomed by touch-dragging with two fingers on mobile
6565
scroll_wheel_zoom False Whether the map can be zoomed by using the mouse wheel

examples/BaseMap.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@
120120
"name": "python",
121121
"nbconvert_exporter": "python",
122122
"pygments_lexer": "ipython3",
123-
"version": "3.8.1"
123+
"version": "3.7.4"
124124
}
125125
},
126126
"nbformat": 4,

examples/CustomProjections.ipynb

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"## New Built-In Projections\n",
8+
"\n",
9+
"ipyleaflet now supports custom map projections and includes 2 base layers for polar projections:\n",
10+
"NASA's Next GenerationBlue Marble 500m for the Arctic and Antarctic regions."
11+
]
12+
},
13+
{
14+
"cell_type": "code",
15+
"execution_count": null,
16+
"metadata": {},
17+
"outputs": [],
18+
"source": [
19+
"from ipyleaflet import (\n",
20+
" Map,\n",
21+
" basemaps,\n",
22+
" basemap_to_tiles,\n",
23+
" Marker,\n",
24+
" TileLayer,\n",
25+
" WMSLayer,\n",
26+
" Polygon,\n",
27+
" GeoJSON,\n",
28+
" DrawControl,\n",
29+
" projections\n",
30+
")\n",
31+
"\n",
32+
"def handle_draw(target, action, geo_json):\n",
33+
" print(action, geo_json)\n",
34+
" \n",
35+
"dc = DrawControl(marker={'shapeOptions': {'color': '#0000FF'}})\n",
36+
"dc.on_draw(handle_draw)\n",
37+
"\n",
38+
"# note that we need to use the same projection for the our layer and the map.\n",
39+
"m1 = Map(center=(90, 0),\n",
40+
" zoom=0,\n",
41+
" basemap=basemaps.NASAGIBS.BlueMarble3413,\n",
42+
" crs=projections.EPSG3413)\n",
43+
"m1.add_control(dc)\n",
44+
"m1"
45+
]
46+
},
47+
{
48+
"cell_type": "markdown",
49+
"metadata": {},
50+
"source": [
51+
"## Custom Projections\n",
52+
"\n",
53+
"Chances are that if you are reading this you want to use a custom projection for your map. This is now supported via [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet). The only thing you need to do is to declare the properties of your projection and Leaflet will do the rest. \n",
54+
"\n",
55+
"Keep in mind that this feature is for predefined projections, so if you use a backend as GeoServer, MapServer etc, they can do the reprojection for you and you can use that projection to show your layers in a map. **ipyleaflet won't reproject your layer on the fly**"
56+
]
57+
},
58+
{
59+
"cell_type": "code",
60+
"execution_count": null,
61+
"metadata": {},
62+
"outputs": [],
63+
"source": [
64+
"# We're going to use https://georepository.com/crs_2163/US-National-Atlas-Equal-Area.html\n",
65+
"my_projection = {\n",
66+
" 'name': 'EPSG:2163',\n",
67+
" 'custom': True, #This is important, it tells ipyleaflet that this projection is not on the predefined ones.\n",
68+
" 'proj4def': '+proj=laea +lat_0=45 +lon_0=-100 +x_0=0 +y_0=0 +a=6370997 +b=6370997 +units=m +no_defs',\n",
69+
" 'origin': [-2215235.82, 420644.41],\n",
70+
" 'bounds': [\n",
71+
" [-8046094.81, 1819060.18],\n",
72+
" [988364.71, 3511186.72] \n",
73+
" ],\n",
74+
" 'resolutions': [\n",
75+
" 8192.0,\n",
76+
" 4096.0,\n",
77+
" 2048.0,\n",
78+
" 1024.0,\n",
79+
" 512.0,\n",
80+
" 256.0\n",
81+
" ]\n",
82+
"}\n",
83+
"\n",
84+
"\n",
85+
"\n",
86+
"wms = WMSLayer(\n",
87+
" url='https://ahocevar.com/geoserver/wms',\n",
88+
" layers='ne:NE1_HR_LC_SR_W_DR',\n",
89+
" format='image/png',\n",
90+
" transparent=True,\n",
91+
" min_zoom=0,\n",
92+
" attribution='ahocevar geospatial',\n",
93+
" crs=my_projection # I'm asking this WMS service to reproject the tile layer using EPSG:2163\n",
94+
")\n",
95+
"\n",
96+
"m2 = Map(center=(40, -104),\n",
97+
" zoom=0,\n",
98+
" layers=(wms,),\n",
99+
" crs=my_projection)\n",
100+
"\n",
101+
"\n",
102+
"dc2 = DrawControl(marker={'shapeOptions': {'color': '#0000FF'}})\n",
103+
"dc2.on_draw(handle_draw)\n",
104+
"\n",
105+
"m2.add_control(dc2)\n",
106+
"\n",
107+
"m2"
108+
]
109+
}
110+
],
111+
"metadata": {
112+
"kernelspec": {
113+
"display_name": "Python 3",
114+
"language": "python",
115+
"name": "python3"
116+
},
117+
"language_info": {
118+
"codemirror_mode": {
119+
"name": "ipython",
120+
"version": 3
121+
},
122+
"file_extension": ".py",
123+
"mimetype": "text/x-python",
124+
"name": "python",
125+
"nbconvert_exporter": "python",
126+
"pygments_lexer": "ipython3",
127+
"version": "3.7.4"
128+
}
129+
},
130+
"nbformat": 4,
131+
"nbformat_minor": 4
132+
}

ipyleaflet/.flake8

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
ignore = E501,E731
2+
exclude =
3+
.git,
4+
__pycache__,
5+
build,
6+
dist
7+
max-complexity = 10
8+

ipyleaflet/basemaps.py

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44

55
from traitlets.utils.bunch import Bunch
66

7+
gibs_attribution = """
8+
Imagery provided by services from the Global Imagery Browse Services (GIBS), operated by the NASA/GSFC/Earth Science Data and Information System (<a href="https://earthdata.nasa.gov">ESDIS</a>) with funding provided by NASA/HQ.
9+
"""
10+
711
basemaps = Bunch(
812
OpenStreetMap=Bunch(
913
Mapnik=dict(
@@ -122,44 +126,62 @@
122126
ModisTerraTrueColorCR=dict(
123127
url='https://gibs.earthdata.nasa.gov/wmts/epsg3857/best/MODIS_Terra_CorrectedReflectance_TrueColor/default/%s/GoogleMapsCompatible_Level9/{z}/{y}/{x}.jpg',
124128
max_zoom=9,
125-
attribution='Imagery provided by services from the Global Imagery Browse Services (GIBS), operated by the NASA/GSFC/Earth Science Data and Information System (<a href="https://earthdata.nasa.gov">ESDIS</a>) with funding provided by NASA/HQ.',
129+
attribution=gibs_attribution,
126130
name='NASAGIBS.ModisTerraTrueColorCR'
127131
),
128132
ModisTerraBands367CR=dict(
129133
url='https://gibs.earthdata.nasa.gov/wmts/epsg3857/best/MODIS_Terra_CorrectedReflectance_Bands367/default/%s/GoogleMapsCompatible_Level9/{z}/{y}/{x}.jpg',
130134
max_zoom=9,
131-
attribution='Imagery provided by services from the Global Imagery Browse Services (GIBS), operated by the NASA/GSFC/Earth Science Data and Information System (<a href="https://earthdata.nasa.gov">ESDIS</a>) with funding provided by NASA/HQ.',
135+
attribution=gibs_attribution,
132136
name='NASAGIBS.ModisTerraBands367CR'
133137
),
134138
ModisTerraBands721CR=dict(
135139
url='https://gibs.earthdata.nasa.gov/wmts/epsg3857/best/MODIS_Terra_CorrectedReflectance_Bands721/default/%s/GoogleMapsCompatible_Level9/{z}/{y}/{x}.jpg',
136140
max_zoom=9,
137-
attribution='Imagery provided by services from the Global Imagery Browse Services (GIBS), operated by the NASA/GSFC/Earth Science Data and Information System (<a href="https://earthdata.nasa.gov">ESDIS</a>) with funding provided by NASA/HQ.',
141+
attribution=gibs_attribution,
138142
name='NASAGIBS.ModisTerraBands721CR'
139143
),
140144
ModisAquaTrueColorCR=dict(
141145
url='https://gibs.earthdata.nasa.gov/wmts/epsg3857/best/MODIS_Aqua_CorrectedReflectance_TrueColor/default/%s/GoogleMapsCompatible_Level9/{z}/{y}/{x}.jpg',
142146
max_zoom=9,
143-
attribution='Imagery provided by services from the Global Imagery Browse Services (GIBS), operated by the NASA/GSFC/Earth Science Data and Information System (<a href="https://earthdata.nasa.gov">ESDIS</a>) with funding provided by NASA/HQ.',
147+
attribution=gibs_attribution,
144148
name='NASAGIBS.ModisAquaTrueColorCR'
145149
),
146150
ModisAquaBands721CR=dict(
147151
url='https://gibs.earthdata.nasa.gov/wmts/epsg3857/best/MODIS_Aqua_CorrectedReflectance_Bands721/default/%s/GoogleMapsCompatible_Level9/{z}/{y}/{x}.jpg',
148152
max_zoom=9,
149-
attribution='Imagery provided by services from the Global Imagery Browse Services (GIBS), operated by the NASA/GSFC/Earth Science Data and Information System (<a href="https://earthdata.nasa.gov">ESDIS</a>) with funding provided by NASA/HQ.',
153+
attribution=gibs_attribution,
150154
name='NASAGIBS.ModisAquaBands721CR'
151155
),
152156
ViirsTrueColorCR=dict(
153157
url='https://gibs.earthdata.nasa.gov/wmts/epsg3857/best/VIIRS_SNPP_CorrectedReflectance_TrueColor/default/%s/GoogleMapsCompatible_Level9/{z}/{y}/{x}.jpg',
154158
max_zoom=9,
155-
attribution='Imagery provided by services from the Global Imagery Browse Services (GIBS), operated by the NASA/GSFC/Earth Science Data and Information System (<a href="https://earthdata.nasa.gov">ESDIS</a>) with funding provided by NASA/HQ.',
159+
attribution=gibs_attribution,
156160
name='NASAGIBS.ViirsTrueColorCR'
157161
),
158162
ViirsEarthAtNight2012=dict(
159163
url='http://gibs.earthdata.nasa.gov/wmts/epsg3857/best/VIIRS_Black_Marble/default/2012-01-01/GoogleMapsCompatible_Level8/{z}/{y}/{x}.png',
160164
max_zoom=8,
161-
attribution='Imagery provided by services from the Global Imagery Browse Services (GIBS), operated by the NASA/GSFC/Earth Science Data and Information System (<a href="https://earthdata.nasa.gov">ESDIS</a>) with funding provided by NASA/HQ.',
165+
attribution=gibs_attribution,
162166
name='NASAGIBS.ViirsEarthAtNight2012'
167+
),
168+
BlueMarble3413=dict(
169+
url='https://gibs.earthdata.nasa.gov/wmts/epsg3413/best/BlueMarble_NextGeneration/default/EPSG3413_500m/{z}/{y}/{x}.jpeg',
170+
max_zoom=5,
171+
attribution=gibs_attribution,
172+
name='NASAGIBS.BlueMarble3413'
173+
),
174+
BlueMarble3031=dict(
175+
url='https://gibs.earthdata.nasa.gov/wmts/epsg3031/best/BlueMarble_NextGeneration/default/EPSG3031_500m/{z}/{y}/{x}.jpeg',
176+
max_zoom=5,
177+
attribution=gibs_attribution,
178+
name='NASAGIBS.BlueMarble3031'
179+
),
180+
BlueMarble=dict(
181+
url='https://gibs.earthdata.nasa.gov/wmts/epsg3857/best/BlueMarble_NextGeneration/default/EPSG3857_500m/{z}/{y}/{x}.jpeg',
182+
max_zoom=8,
183+
attribution=gibs_attribution,
184+
name='NASAGIBS.BlueMarble'
163185
)
164186
),
165187
Strava=Bunch(

ipyleaflet/leaflet.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@
2323

2424
from ._version import EXTENSION_VERSION
2525

26+
from .projections import projections
27+
2628
def_loc = [0.0, 0.0]
27-
allowed_crs = ['Earth', 'EPSG3395', 'EPSG3857', 'EPSG4326', 'Base', 'Simple']
2829
allowed_cursor = ['alias', 'cell', 'grab', 'move', 'crosshair', 'context-menu',
2930
'n-resize', 'ne-resize', 'e-resize', 'se-resize', 's-resize',
3031
'sw-resize', 'w-resize', 'nw-resize', 'nesw-resize',
@@ -299,7 +300,7 @@ class WMSLayer(TileLayer):
299300
styles = Unicode().tag(sync=True, o=True)
300301
format = Unicode('image/jpeg').tag(sync=True, o=True)
301302
transparent = Bool(False).tag(sync=True, o=True)
302-
crs = Enum(values=allowed_crs, default_value='EPSG3857').tag(sync=True)
303+
crs = Dict(default_value=projections.EPSG3857).tag(sync=True)
303304
uppercase = Bool(False).tag(sync=True, o=True)
304305

305306

@@ -969,7 +970,7 @@ class Map(DOMWidget, InteractMixin):
969970
max_zoom = CFloat(18).tag(sync=True, o=True)
970971
min_zoom = CFloat(1).tag(sync=True, o=True)
971972
interpolation = Unicode('bilinear').tag(sync=True, o=True)
972-
crs = Enum(values=allowed_crs, default_value='EPSG3857').tag(sync=True)
973+
crs = Dict(default_value=projections.EPSG3857).tag(sync=True)
973974

974975
# Specification of the basemap
975976
basemap = Union(

ipyleaflet/projections.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
from traitlets.utils.bunch import Bunch
2+
3+
projections = Bunch(
4+
EPSG3857=dict(
5+
name='EPSG3857',
6+
custom=False,
7+
),
8+
Earth=dict(
9+
name='Earth',
10+
custom=False
11+
),
12+
EPSG3395=dict(
13+
name='EPSG3395',
14+
custom=False
15+
),
16+
EPSG4326=dict(
17+
name='EPSG4326',
18+
custom=False
19+
),
20+
Base=dict(
21+
name='Base',
22+
custom=False
23+
),
24+
Simple=dict(
25+
name='Simple',
26+
custom=False
27+
),
28+
EPSG3413=dict(
29+
name='EPSG3413',
30+
custom=True,
31+
proj4def="""+proj=stere +lat_0=90 +lat_ts=70 +lon_0=-45 +k=1 +x_0=0 +y_0=0
32+
+ellps=WGS84 +datum=WGS84 +units=m +no_defs""",
33+
# origin and bounds for 500m resolution
34+
origin=[-4194304 / 2, 4194304 / 2],
35+
resolutions=[
36+
8192.0,
37+
4096.0,
38+
2048.0,
39+
1024.0,
40+
512.0,
41+
256.0
42+
],
43+
bounds=[
44+
[-4194304 / 2, -4194304 / 2],
45+
[4194304 / 2, 4194304 / 2]
46+
]
47+
),
48+
EPSG3031=dict(
49+
name='EPSG3031',
50+
custom=True,
51+
proj4def="""+proj=stere +lat_0=-90 +lat_ts=-71 +lon_0=0 +k=1 +x_0=0 +y_0=0
52+
+ellps=WGS84 +datum=WGS84 +units=m +no_defs""",
53+
# origin and bounds for 500m resolution
54+
origin=[-4194304 / 2, 4194304 / 2],
55+
resolutions=[
56+
8192.0,
57+
4096.0,
58+
2048.0,
59+
1024.0,
60+
512.0,
61+
256.0
62+
],
63+
bounds=[
64+
[-4194304 / 2, -4194304 / 2],
65+
[4194304 / 2, 4194304 / 2]
66+
]
67+
)
68+
)

0 commit comments

Comments
 (0)