Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OSM tiling #440

Merged
merged 48 commits into from
Sep 22, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
9eaa699
Working on osm...
dalmijn Feb 15, 2023
5c348d2
Merge branch 'main' into osm
dalmijn Feb 15, 2023
297212d
Merge branch 'main' into osm
dalmijn Feb 17, 2023
f36b3dd
Merge branch 'main' into osm
dalmijn Feb 22, 2023
2278dd3
Merge branch 'main' into osm
dalmijn May 15, 2023
3e860b6
Merge branch 'main' into osm
dalmijn Jun 29, 2023
b377106
Merge branch 'main' into osm
dalmijn Jul 10, 2023
955e859
Added 'to_osm_tiles'
dalmijn Jul 12, 2023
6bea417
Merge branch 'main' into osm
dalmijn Jul 12, 2023
9bc42d0
Cleared output, deleted unused variable
dalmijn Jul 12, 2023
951d3e1
Restored showing of DataCatalog
dalmijn Jul 12, 2023
c1ab0e3
Linting...
dalmijn Jul 12, 2023
ed12d3d
Removed whitespace
dalmijn Jul 12, 2023
149d420
Readded tmpdir
dalmijn Jul 12, 2023
da11a4e
Added OSM tiling test
dalmijn Jul 12, 2023
bb08273
No concats in paths..
dalmijn Jul 12, 2023
f884bbf
Chunking like this blows up memory, removed it
dalmijn Jul 13, 2023
7836a40
Properly induce the reprojected pixel size of the source data
dalmijn Jul 14, 2023
08db166
Merge branch 'main' into osm
dalmijn Jul 26, 2023
1e8fa3f
Updated changelog post v0.8.0 release
dalmijn Jul 26, 2023
a85d458
snake_case to_osm
dalmijn Aug 11, 2023
206b587
Merge branch 'main' into osm
dalmijn Aug 11, 2023
dc01c39
moved 'create_folder'
dalmijn Aug 11, 2023
c15ee9b
small osm fixes
dalmijn Aug 11, 2023
d21e47a
Added small docstring to 'create_folder'
dalmijn Aug 11, 2023
3ed491a
Added a period (apparently...)
dalmijn Aug 11, 2023
6707107
Improved readability; added a tiling function that caters to webviewers
dalmijn Sep 14, 2023
371b71c
Merge branch 'main' into osm
dalmijn Sep 14, 2023
47ff64d
Fixed dependency; linting stuff
dalmijn Sep 14, 2023
cbd1ff0
linting.....
dalmijn Sep 14, 2023
5f9c17b
remove whitespace
dalmijn Sep 14, 2023
45bf02e
remove unnecessary create_folder method
DirkEilander Sep 20, 2023
f948149
simplify slippy tiles
DirkEilander Sep 21, 2023
e865cc0
add optional dependencies
DirkEilander Sep 21, 2023
5579d40
Merge branch 'main' into osm
DirkEilander Sep 22, 2023
f603d8e
test and small bugfixes; update changelog
DirkEilander Sep 22, 2023
b9abc00
move mercantile back to deps for docs; improve test coverage
DirkEilander Sep 22, 2023
eef06e5
fix test after including extra zoom lvl
DirkEilander Sep 22, 2023
6d353fd
Merge branch 'main' into osm
DirkEilander Sep 22, 2023
0c65f5d
remove unecessary flipud
DirkEilander Sep 22, 2023
1f1a0ed
fix stack level
savente93 Sep 22, 2023
d1f432e
Updated version
dalmijn Sep 22, 2023
56c4b4e
Revamped zoom level determination
dalmijn Sep 22, 2023
e6e1162
Added extra example for the oil painting
dalmijn Sep 22, 2023
a0ba124
some docs for the colormap
dalmijn Sep 22, 2023
180f24f
Merge branch 'osm' of https://github.com/Deltares/hydromt into osm
dalmijn Sep 22, 2023
8135548
Restored yaml filename to folder; pinned zl in example
dalmijn Sep 22, 2023
2155a2f
Some last simplifications
dalmijn Sep 22, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 56 additions & 6 deletions examples/working_with_tiled_raster_data.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -151,12 +151,10 @@
"from matplotlib import pyplot as plt\n",
"\n",
"\n",
"name_png = f\"{source}_png_cmap\"\n",
"name_png = f\"{source}_png\"\n",
"root_png = join(\"tmpdir\", name_png)\n",
"da0.raster.to_slippy_tiles(\n",
" root=root_png,\n",
" cmap=\"terrain\",\n",
" norm=plt.Normalize(vmin=0, vmax=2000),\n",
")"
]
},
Expand Down Expand Up @@ -199,7 +197,59 @@
"\n",
"If one were to see what this would look like in e.g. QGIS, a local sever is needed. \n",
"With python this is easily done with the command `python -m http.server 8000` from the command line while within the folder where the tiles are located. In this case that would be 'root_png'.\n",
"In QGIS, make a new XYZ Tiles connection. For this new connection the URL becomes 'http://localhost:8000/{z}/{x}/{y}.png' and the interpolation is set to Terrarium Terrain RGB.\n"
"In QGIS, make a new XYZ Tiles connection. For this new connection the URL becomes 'http://localhost:8000/{z}/{x}/{y}.png' and the interpolation is set to Terrarium Terrain RGB.\n",
"\n",
"However, if the images are meant to be viewed as is, then a custom colormap can be defined to make them look nice!\n",
"\n",
"Let's make another dataset of png's!\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from matplotlib import pyplot as plt\n",
"\n",
"\n",
"name_png_cmap = f\"{source}_png_cmap\"\n",
"root_png_cmap = join(\"tmpdir\", name_png_cmap)\n",
"# let us define a nice color for a terrain image\n",
"da0.raster.to_slippy_tiles(\n",
" root=root_png_cmap,\n",
" cmap=\"terrain\",\n",
" norm=plt.Normalize(vmin=0, vmax=2000),\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now lets take a look at the improved standard visuals!"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# View the data\n",
"from PIL import Image\n",
"import matplotlib.pyplot as plt\n",
"\n",
"# Create a figure to show the image\n",
"fig = plt.figure()\n",
"fig.set_size_inches(2.5, 2.5)\n",
"ax = fig.add_subplot(111)\n",
"ax.set_position([0, 0, 1, 1])\n",
"ax.axis(\"off\")\n",
"\n",
"# Show the image\n",
"im = Image.open(join(root_png_cmap, \"9\", \"273\", \"182.png\"))\n",
"ax.imshow(im)"
]
},
{
Expand Down Expand Up @@ -258,7 +308,7 @@
"outputs": [],
"source": [
"# And for OSM\n",
"da1 = data_catalog.get_rasterdataset(name_osm)\n",
"da1 = data_catalog.get_rasterdataset(name_osm, zoom_level=11)\n",
"da1.raster.shape"
]
},
Expand Down Expand Up @@ -355,7 +405,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.4"
"version": "3.11.5"
},
"orig_nbformat": 4,
"vscode": {
Expand Down
72 changes: 59 additions & 13 deletions hydromt/raster.py
Original file line number Diff line number Diff line change
Expand Up @@ -2271,8 +2271,8 @@ def to_slippy_tiles(
min_lvl: int = None,
max_lvl: int = None,
driver="png",
cmap=None,
norm=None,
cmap: str | object = None,
norm: object = None,
**kwargs,
):
"""Produce tiles in /zoom/x/y.<ext> structure (EPSG:3857).
Expand All @@ -2292,6 +2292,13 @@ def to_slippy_tiles(
If None, the zoomlevels will be determined based on the data resolution
driver : str, optional
file output driver, one of 'png', 'netcdf4' or 'GTiff'
cmap : str | object, optional
A colormap, either defined by a string and imported from matplotlib
via that string or as a ListedColormap object from matplotlib itself.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
via that string or as a ListedColormap object from matplotlib itself.
via that string or as a Colormap object from matplotlib itself.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The object is called a ListedColormap, but I will change it.

norm : object, optional
A matplotlib Normalize object that defines a range between a maximum
and minimum value

**kwargs
Key-word arguments to write file
for netcdf4, these are passed to ~:py:meth:xarray.DataArray.to_netcdf:
Expand All @@ -2309,6 +2316,10 @@ def to_slippy_tiles(

# Fixed pixel size and CRS for XYZ tiles
pxs = 256
# Extent in y-direction for pseudo mercator (EPSG:3857)
y_ext = math.atan(math.sinh(math.pi)) * (180 / math.pi)
y_ext_pm = mct.xy(0, y_ext)[1]

crs = CRS.from_epsg(3857)
ext = {"png": "png", "netcdf4": "nc", "gtiff": "tif"}.get(driver.lower(), None)
if ext is None:
Expand All @@ -2322,6 +2333,8 @@ def to_slippy_tiles(
# make sure dataarray has a name
name = obj.name or "data"
obj.name = name
obj_res = obj.raster.res[0]
obj_bounds = list(obj.raster.bounds)

# colormap output
if cmap is not None and driver != "png":
Expand All @@ -2340,28 +2353,61 @@ def to_slippy_tiles(
}.get(driver.lower(), {})
kwargs = {**kwargs0, **kwargs}

# Setting up information for zoomlevel calculation and
# determination of tile windows
# This section is purely for the resolution
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this section be moved inside the if max_lvl is None: statement?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

bounds_4326_clip = list(obj.raster.transform_bounds("EPSG:4326"))
bounds_4326_clip[:2] = map(
max,
zip(bounds_4326_clip[:2], (-180, -y_ext)),
)
bounds_4326_clip[2:] = map(
min,
zip(bounds_4326_clip[2:], (180, y_ext)),
)
obj_clipped_to_pseudo = obj.raster.clip_bbox(
bounds_4326_clip,
crs="EPSG:4326",
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this do the same? Or am I missing something?

Suggested change
bounds_4326_clip = list(obj.raster.transform_bounds("EPSG:4326"))
bounds_4326_clip[:2] = map(
max,
zip(bounds_4326_clip[:2], (-180, -y_ext)),
)
bounds_4326_clip[2:] = map(
min,
zip(bounds_4326_clip[2:], (180, y_ext)),
)
obj_clipped_to_pseudo = obj.raster.clip_bbox(
bounds_4326_clip,
crs="EPSG:4326",
)
obj_clipped_to_pseudo = obj.raster.clip_bbox(
[-180, -y_ext, 180, y_ext],
crs="EPSG:4326",
)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I just wrote it way too explicitly..., kind of laughed when I saw this.

tr_3857 = rasterio.warp.calculate_default_transform(
obj_clipped_to_pseudo.raster.crs,
"EPSG:3857",
*obj_clipped_to_pseudo.shape,
*obj_clipped_to_pseudo.raster.bounds,
)[0]

del obj_clipped_to_pseudo

# This section is for dealing with rounding errors
obj_bounds_cor = [
obj_bounds[0] + 0.5 * obj_res,
obj_bounds[1] + 0.5 * obj_res,
obj_bounds[2] - 0.5 * obj_res,
obj_bounds[3] - 0.5 * obj_res,
]
bounds_4326 = rasterio.warp.transform_bounds(
obj.raster.crs,
"EPSG:4326",
*obj_bounds_cor,
)

# Calculate min/max zoomlevel based
bounds_wgsg84 = obj.raster.transform_bounds("EPSG:4326")
if max_lvl is None: # calculate max zoomlevel close to native resolution
max_lat = max(np.abs(bounds_wgsg84[1]), np.abs(bounds_wgsg84[3]))
if self.crs.is_projected and self.crs.axis_info[0].unit_name == "metre":
dx_m = obj.raster.res[0]
else:
dx_m = gis_utils.cellres(max_lat, *obj.raster.res)[0]
C = 2 * np.pi * 6378137 # circumference of the earth
# Determine the max number of zoom levels with the resolution
dres = tr_3857[0]
max_lvl = int(
np.ceil(np.log2(C * np.cos(np.deg2rad(max_lat)) / dx_m / pxs))
math.ceil((math.log10((y_ext_pm * 2) / (dres * pxs)) / math.log10(2)))
)
if min_lvl is None: # calculate min zoomlevel based on the data extent
min_lvl = mct.bounding_tile(*bounds_wgsg84).z
min_lvl = mct.bounding_tile(*bounds_4326).z

# Loop through the zoom levels
zoom_levels = {}
logger.info(f"Producing tiles from zoomlevel {min_lvl} to {max_lvl}")
for zl in range(max_lvl, min_lvl - 1, -1):
fns = []
# Go through the zoomlevels
for i, tile in enumerate(mct.tiles(*bounds_wgsg84, zl, truncate=True)):
for i, tile in enumerate(mct.tiles(*bounds_4326, zl, truncate=True)):
ssd = Path(root, str(zl), f"{tile.x}")
os.makedirs(ssd, exist_ok=True)
tile_bounds = mct.xy_bounds(tile)
Expand Down Expand Up @@ -2460,7 +2506,7 @@ def to_slippy_tiles(
"zoom_levels": zoom_levels,
}
name = os.path.basename(root)
with open(join(root, "data.yml"), "w") as f:
with open(join(root, f"{name}.yml"), "w") as f:
yaml.dump({name: yml}, f, default_flow_style=False, sort_keys=False)

def to_raster(
Expand Down
Loading