Skip to content

Commit

Permalink
make ranges public, fix querying tile forgot to flip (WHOOPS!) (#126)
Browse files Browse the repository at this point in the history
* added examples to utiles of basic mbtiles usage

* ranges public and fix flip get/has tile impls"

* weirdness with rust 1.80.0'
  • Loading branch information
jessekrubin authored Jul 25, 2024
1 parent bb047eb commit a687622
Show file tree
Hide file tree
Showing 11 changed files with 85 additions and 62 deletions.
9 changes: 4 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 25 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
[workspace]
resolver = "2"
members = [
"crates/utiles",
"crates/utiles-core", "crates/utiles-oxipng",
"utiles-pyo3",
"crates/utiles",
"crates/utiles-core",
"crates/utiles-oxipng",
"utiles-pyo3",
]

[workspace.package]
version = "0.7.0-alpha.3"
version = "0.7.0-alpha.4"
authors = [
"Jesse Rubin <[email protected]>",
"Dan Costello <[email protected]>",
"Jesse Rubin <[email protected]>",
"Dan Costello <[email protected]>",
]
documentation = "https://github.com/jessekrubin/utiles"
edition = "2021"
Expand Down Expand Up @@ -44,6 +45,19 @@ tracing = "0.1.40"
tracing-subscriber = { version = "0.3.17", features = ["serde", "serde_json", "env-filter"] }
xxhash-rust = { version = "0.8.10", features = ["xxh32", "xxh64", "xxh3", "const_xxh32", "const_xxh64", "const_xxh3"] }

# Config for 'cargo dist'
[workspace.metadata.dist]
# The preferred cargo-dist version to use in CI (Cargo.toml SemVer syntax)
cargo-dist-version = "0.19.1"
# CI backends to support
ci = "github"
# The installers to generate for each app
installers = []
# Target platforms to build apps for (Rust target-triple syntax)
targets = ["aarch64-apple-darwin", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "x86_64-pc-windows-msvc"]
# Publish jobs to run in CI
pr-run-mode = "plan"

[profile.dev]
opt-level = 0

Expand All @@ -54,4 +68,9 @@ opt-level = 2
opt-level = 3
strip = true
lto = "thin"

# The profile that 'cargo dist' will build with
[profile.dist]
inherits = "release"
lto = "thin"
# lto = "fat"
45 changes: 14 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ utiles = utils + tiles
## Installation

```bash
# __CLI__
# from crates
cargo install utiles
# from source
cargo install --git https://github.com/jessekrubin/utiles.git utiles # and/or utiles-oxipng
# via the python package (which wrappers the rust-cli)
pip install -U utiles

# __LIBS__
# python (python lib + rust-cli)
pip install -U utiles
# rust-cli
Expand All @@ -13,38 +22,20 @@ cargo install utiles
cargo add utiles-core utiles
```

## python

A mostly drop-in replacement for [mercantile](https://github.com/mapbox/mercantile) written w/ rust, plus several other util(e)ities

[py-utiles](https://github.com/jessekrubin/utiles/tree/main/utiles-pyo3)

## About

`utiles` started off as a python port of mapbox's web-mercator utils
python-library [mercantile](https://github.com/mapbox/mercantile) written
in rust. It has since been expanded into a slim rust crate (`utiles-core`)
a less slim crate with a lib/cli (`utiles`), and the python wrapper package.
a less slim crate with a lib/cli (`utiles`), that has a python package wrapper.

For more details on the python package see: [./utiles-pyo3](https://github.com/jessekrubin/utiles/tree/main/utiles-pyo3)

### Why?

I use mercantile regularly and wished it were a bit more ergonomic, had type annotations, and was faster, but overall
it's a great library.

This was an excuse to learn some more rust as well as pyo3.

**Do I/you REALLY need a rust-port of mercantile?**

I don't know, decide for yourself. `utiles` is certainly faster than `mercantile` for some things (see benchmarks below)

**Is it really a drop in replacement for mercantile?**
## python

Not quite, but it's close. utiles doesn't throw the same exceptions as mercantile, instead it throws `ValueError`'s and
`TypeError`'s.
A mostly drop-in replacement for [mercantile](https://github.com/mapbox/mercantile) written w/ rust, plus several other util(e)ities

There might be other differences, but I have been using it instead of mercantile for a bit now and it works pretty decent, tho I am open to suggestions!
[py-utiles](https://github.com/jessekrubin/utiles/tree/main/utiles-pyo3)

---

Expand All @@ -54,17 +45,9 @@ There might be other differences, but I have been using it instead of mercantile

- Please do! Would love some feedback!
- Be kind!
- DO NOT USE the phrases `blazing fast`/`blazingly fast` in any PRs, issues or docs.
- I will happily accept PRs, and add you to the currently (5/26/2023) non-existent contributors list.

## TODO:

- [x] benchmark against mercantile
- **Maybe:**
- [x] Split library into `utiles` (rust lib) and `utiles-python` (python/pip package)?
- [] Mbtiles support??
- [] Reading/writing mvt files?
- [] Re-write cli in rust with clap?

---

## MISC
Expand Down
1 change: 0 additions & 1 deletion crates/utiles-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,3 @@ fast_hilbert.workspace = true
serde.workspace = true
serde_json.workspace = true
thiserror.workspace = true
log = "0.4.22"
2 changes: 1 addition & 1 deletion crates/utiles-core/src/bbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ impl BBox {
/// containing a single `BBox` that represents the current instance itself.
///
/// # Returns
/// - `Vec<BBox>`: A vector containing one `BBox` if the instance does not cross the antimeridian,
/// `Vec<BBox>`: A vector containing one `BBox` if the instance does not cross the antimeridian,
/// or two `BBox`es if it does.
///
/// # Examples
Expand Down
8 changes: 4 additions & 4 deletions crates/utiles-core/src/tile_zbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ use crate::{Point2d, TileLike};
/// A struct representing a bbox of tiles at a specific zoom level
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TileZBox {
zoom: u8,
min: Point2d<u32>,
max: Point2d<u32>,
pub zoom: u8,
pub min: Point2d<u32>,
pub max: Point2d<u32>,
}

/// A struct representing a set of `TileZBox`es
#[derive(Debug)]
pub struct TileZBoxes {
ranges: Vec<TileZBox>,
pub ranges: Vec<TileZBox>,
}

/// An iterator over a `TileZBox` that yields tiles
Expand Down
1 change: 1 addition & 0 deletions crates/utiles/src/cli/args.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// #![allow(clippy::doc_lazy_continuation)]
use clap::{Args, Parser, Subcommand};
use std::path::PathBuf;
use strum_macros::AsRefStr;
Expand Down
25 changes: 17 additions & 8 deletions crates/utiles/src/mbt/mbtiles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use tracing::{debug, error, warn};
use utiles_core::bbox::BBox;
use utiles_core::constants::MBTILES_MAGIC_NUMBER;
use utiles_core::tile_data_row::TileData;
use utiles_core::{yflip, LngLat, Tile, TileLike};
use utiles_core::{flipy, yflip, LngLat, Tile, TileLike};

use crate::errors::UtilesResult;
use crate::mbt::query::{
Expand Down Expand Up @@ -647,12 +647,26 @@ pub fn query_zxy(
y: u32,
) -> RusqliteResult<Option<Vec<u8>>> {
let mut stmt = connection.prepare_cached("SELECT tile_data FROM tiles WHERE zoom_level=?1 AND tile_column=?2 AND tile_row=?3")?;
let yup = flipy(y, z);
let tile_data: Option<Vec<u8>> = stmt
.query_row(params![z, x, y], |row| row.get(0))
.query_row(params![z, x, yup], |row| row.get(0))
.optional()?;
Ok(tile_data)
}

pub fn has_zxy(connection: &Connection, z: u8, x: u32, y: u32) -> RusqliteResult<bool> {
let mut stmt = connection.prepare_cached("SELECT COUNT(*) FROM tiles WHERE zoom_level=?1 AND tile_column=?2 AND tile_row=?3 LIMIT 1")?;
let yup = flipy(y, z);
let count: i64 = stmt.query_row(params![z, x, yup], |row| row.get(0))?;
Ok(count == 1)
}
pub fn has_tile<T: TileLike>(
connection: &Connection,
tile: &T,
) -> RusqliteResult<bool> {
has_zxy(connection, tile.z(), tile.x(), tile.y())
}

pub fn query_tile<T: TileLike>(
connection: &Connection,
tile: &T,
Expand All @@ -664,12 +678,7 @@ pub fn tile_exists<T: TileLike>(
connection: &Connection,
tile: &T,
) -> RusqliteResult<bool> {
let mut stmt = connection.prepare_cached("SELECT COUNT(*) FROM tiles WHERE zoom_level=?1 AND tile_column=?2 AND tile_row=?3")?;
let rows = stmt.query_row(params![tile.z(), tile.x(), tile.flipy()], |row| {
let count: i64 = row.get(0)?;
Ok(count)
})?;
Ok(rows == 1_i64)
has_tile(connection, tile)
}

pub fn tiles_is_empty(connection: &Connection) -> RusqliteResult<bool> {
Expand Down
5 changes: 5 additions & 0 deletions crates/utiles/src/mbt/mbtiles_async.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,17 @@ pub trait MbtilesAsync: Sized {
async fn metadata_minzoom(&self) -> UtilesResult<Option<u8>>;
async fn metadata_maxzoom(&self) -> UtilesResult<Option<u8>>;

async fn has_zxy(&self, z: u8, x: u32, y: u32) -> UtilesResult<bool>;
async fn query_zxy(&self, z: u8, x: u32, y: u32) -> UtilesResult<Option<Vec<u8>>>;

async fn query_tile(&self, tile: &Tile) -> UtilesResult<Option<Vec<u8>>> {
self.query_zxy(tile.z(), tile.x(), tile.y()).await
}

async fn has_tile(&self, tile: &Tile) -> UtilesResult<bool> {
self.has_zxy(tile.z(), tile.x(), tile.y()).await
}

async fn query_minzoom_maxzoom(&self) -> UtilesResult<Option<MinZoomMaxZoom>>;
async fn query_tilelike<T: TileLike + Send>(
&self,
Expand Down
13 changes: 10 additions & 3 deletions crates/utiles/src/mbt/mbtiles_async_sqlite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ use utiles_core::BBox;
use crate::errors::UtilesResult;
use crate::mbt::mbtiles::{
add_functions, has_metadata_table_or_view, has_tiles_table_or_view,
has_zoom_row_col_index, init_mbtiles, mbtiles_metadata, mbtiles_metadata_row,
metadata_json, minzoom_maxzoom, query_zxy, tiles_count, tiles_is_empty,
has_zoom_row_col_index, has_zxy, init_mbtiles, mbtiles_metadata,
mbtiles_metadata_row, metadata_json, minzoom_maxzoom, query_zxy, tiles_count,
tiles_is_empty,
};
use crate::mbt::mbtiles_async::MbtilesAsync;
use crate::mbt::query::query_mbtiles_type;
Expand Down Expand Up @@ -453,13 +454,19 @@ where
None => Ok(None),
}
}
async fn has_zxy(&self, z: u8, x: u32, y: u32) -> UtilesResult<bool> {
let res = self
.conn(move |conn| has_zxy(conn, z, x, y))
.await
.map_err(UtilesError::AsyncSqliteError)?;
Ok(res)
}

async fn query_zxy(&self, z: u8, x: u32, y: u32) -> UtilesResult<Option<Vec<u8>>> {
let tile = self
.conn(move |conn| query_zxy(conn, z, x, y))
.await
.map_err(UtilesError::AsyncSqliteError)?;

Ok(tile)
}

Expand Down
7 changes: 4 additions & 3 deletions utiles-pyo3/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
![Python Version from PEP 621 TOML](https://img.shields.io/python/required-version-toml?tomlFilePath=https%3A%2F%2Fraw.githubusercontent.com%2Fjessekrubin%2Futiles%2Fmain%2Futiles-pyo3%2Fpyproject.toml&style=flat-square&logo=python&logoColor=white&color=blue)
[![Wheel](https://img.shields.io/pypi/wheel/utiles.svg?style=flat-square)](https://img.shields.io/pypi/wheel/utiles.svg)

utiles = utils + tiles
`utiles = utils + tiles` OR `utiles = ultra-tiles` depending on the day.

Fast spherical mercator geo/tile util(e)ities.

Expand Down Expand Up @@ -140,7 +140,8 @@ test_ul_bench[mercantile-(486, 332, 20)] 1,099.9938 (5.38) 107,300.0021
## TODO:

- [x] benchmark against mercantile
- [x] Re-write cli in rust with clap
- [x] Split library into `utiles` (rust lib) and `utiles-python` (python/pip package)?
- [x] Re-write cli in rust with clap?
- **Maybe:**
- [] Mbtiles support??
- [] Mbtiles support for the python lib??
- [] Reading/writing mvt files?

0 comments on commit a687622

Please sign in to comment.