mirror of
https://github.com/maplibre/maplibre-rs.git
synced 2025-12-08 19:05:57 +00:00
Squashed 'libs/mvt/' content from commit 3dd127f
git-subtree-dir: libs/mvt git-subtree-split: 3dd127f6e199359aaa3530c9bbebb334beed0b9b
This commit is contained in:
commit
5359e7b79d
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
/target
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
||||
71
CHANGELOG.md
Normal file
71
CHANGELOG.md
Normal file
@ -0,0 +1,71 @@
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
* Moved `BBox` to `pointy` crate
|
||||
* `GeomEncoder::point` / `add_point` are now fallible (float to int errors)
|
||||
* `GeomEncoder` now has a `Float` type parameter (`f32` or `f64`)
|
||||
* `MapGrid` now has a `Float` type parameter (`f32` or `f64`)
|
||||
* Updated `protobuf` dependency to version 2.25
|
||||
|
||||
## [0.7.0] - 2020-09-29
|
||||
### Changed
|
||||
* Replaced `geom` module with `pointy` crate dependency
|
||||
|
||||
## [0.6.0] - 2020-09-18
|
||||
### Changed
|
||||
* Implement Default for Layer
|
||||
* Made Error enum non-exhaustive
|
||||
* Replaced `MapGrid::new_web_mercator()` with `MapGrid::default()`
|
||||
|
||||
## [0.5.4] - 2020-09-11
|
||||
### Added
|
||||
* Use `cargo run --features=update` to update to a new protobuf version
|
||||
### Changed
|
||||
* Updated to protobuf 2.17
|
||||
|
||||
## [0.5.3] - 2019-10-30
|
||||
### Changed
|
||||
* Updated protobuf dependency
|
||||
|
||||
## [0.5.2] - 2019-02-28
|
||||
### Added
|
||||
* Layer::name() method
|
||||
|
||||
## [0.5.1] - 2019-02-22
|
||||
### Changed
|
||||
* Made MapGrid cloneable
|
||||
* Made Tile::compute_size() public
|
||||
|
||||
## [0.5.0] - 2019-02-14
|
||||
### Added
|
||||
* Feature::layer and ::num_tags methods
|
||||
* Error::Other
|
||||
### Changed
|
||||
* Feature::set_id can no longer fail
|
||||
### Removed
|
||||
* Error::DuplicateId
|
||||
|
||||
## [0.4.0] - 2019-02-07
|
||||
### Added
|
||||
* GeomEncoder::point and ::complete (for method chaining)
|
||||
### Changed
|
||||
* GeomEncoder::add_point and ::complete_geom now take a reference
|
||||
|
||||
## [0.3.0] - 2019-01-18
|
||||
### Added
|
||||
* MapGrid, TileId and BBox
|
||||
* New error variant: InvalidTid
|
||||
|
||||
## [0.2.0] - 2019-01-11
|
||||
### Added
|
||||
* Check extent when adding layer to tile
|
||||
* GeomEncoder now has encode method to create GeomData struct
|
||||
* New error variant: InvalidGeometry
|
||||
|
||||
### Changed
|
||||
* GeomEncoder now uses builder pattern
|
||||
* Made Tile::compute_size private
|
||||
* Tile::get_extent() => extent()
|
||||
|
||||
## [0.1.0] - 2019-01-10
|
||||
* Initial version
|
||||
27
Cargo.toml
Normal file
27
Cargo.toml
Normal file
@ -0,0 +1,27 @@
|
||||
[package]
|
||||
name = "mvt"
|
||||
version = "0.7.0"
|
||||
description = "A library for encoding mapbox vector tiles"
|
||||
license = "MIT OR Apache-2.0"
|
||||
documentation = "https://docs.rs/mvt"
|
||||
repository = "https://github.com/DougLau/mvt"
|
||||
readme = "README.md"
|
||||
keywords = ["cartography", "gis", "vector", "tile", "mapbox"]
|
||||
categories = ["encoding"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
num-traits = "0.2"
|
||||
pointy = "0.3"
|
||||
protobuf = "~2.25"
|
||||
protobuf-codegen-pure = { version = "2.25", optional = true }
|
||||
thiserror = "1"
|
||||
|
||||
[features]
|
||||
update = ["protobuf-codegen-pure"]
|
||||
|
||||
[[bin]]
|
||||
# `cargo run --features=update` when updating to a new protobuf version
|
||||
name = "mvt"
|
||||
required-features = ["update"]
|
||||
201
LICENSE-APACHE
Normal file
201
LICENSE-APACHE
Normal file
@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
21
LICENSE-MIT
Normal file
21
LICENSE-MIT
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Minnesota Department of Transportation
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
11
README.md
Normal file
11
README.md
Normal file
@ -0,0 +1,11 @@
|
||||
# mvt
|
||||
A Rust library for encoding [mapbox vector tiles]. Decoding is not implemented.
|
||||
|
||||
The [API] is designed to prevent creating files which are not allowed by the
|
||||
specification.
|
||||
|
||||
Version 2.1 of the standard is supported.
|
||||
|
||||
|
||||
[API]: https://docs.rs/mvt
|
||||
[mapbox vector tiles]: https://github.com/mapbox/vector-tile-spec
|
||||
23
examples/simple.rs
Normal file
23
examples/simple.rs
Normal file
@ -0,0 +1,23 @@
|
||||
use mvt::{Error, GeomEncoder, GeomType, Tile};
|
||||
use pointy::Transform;
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
let mut tile = Tile::new(4096);
|
||||
let layer = tile.create_layer("First Layer");
|
||||
// NOTE: normally, the Transform would come from MapGrid::tile_transform
|
||||
let b = GeomEncoder::new(GeomType::Linestring, Transform::default())
|
||||
.point(0.0, 0.0)?
|
||||
.point(1024.0, 0.0)?
|
||||
.point(1024.0, 2048.0)?
|
||||
.point(2048.0, 2048.0)?
|
||||
.point(2048.0, 4096.0)?
|
||||
.encode()?;
|
||||
let mut feature = layer.into_feature(b);
|
||||
feature.set_id(1);
|
||||
feature.add_tag_string("key", "value");
|
||||
let layer = feature.into_layer();
|
||||
tile.add_layer(layer)?;
|
||||
let data = tile.to_bytes()?;
|
||||
println!("encoded {} bytes: {:?}", data.len(), data);
|
||||
Ok(())
|
||||
}
|
||||
78
protos/vector_tile.proto
Normal file
78
protos/vector_tile.proto
Normal file
@ -0,0 +1,78 @@
|
||||
package vector_tile;
|
||||
|
||||
option optimize_for = LITE_RUNTIME;
|
||||
|
||||
message Tile {
|
||||
|
||||
// GeomType is described in section 4.3.4 of the specification
|
||||
enum GeomType {
|
||||
UNKNOWN = 0;
|
||||
POINT = 1;
|
||||
LINESTRING = 2;
|
||||
POLYGON = 3;
|
||||
}
|
||||
|
||||
// Variant type encoding
|
||||
// The use of values is described in section 4.1 of the specification
|
||||
message Value {
|
||||
// Exactly one of these values must be present in a valid message
|
||||
optional string string_value = 1;
|
||||
optional float float_value = 2;
|
||||
optional double double_value = 3;
|
||||
optional int64 int_value = 4;
|
||||
optional uint64 uint_value = 5;
|
||||
optional sint64 sint_value = 6;
|
||||
optional bool bool_value = 7;
|
||||
|
||||
extensions 8 to max;
|
||||
}
|
||||
|
||||
// Features are described in section 4.2 of the specification
|
||||
message Feature {
|
||||
optional uint64 id = 1 [ default = 0 ];
|
||||
|
||||
// Tags of this feature are encoded as repeated pairs of
|
||||
// integers.
|
||||
// A detailed description of tags is located in sections
|
||||
// 4.2 and 4.4 of the specification
|
||||
repeated uint32 tags = 2 [ packed = true ];
|
||||
|
||||
// The type of geometry stored in this feature.
|
||||
optional GeomType type = 3 [ default = UNKNOWN ];
|
||||
|
||||
// Contains a stream of commands and parameters (vertices).
|
||||
// A detailed description on geometry encoding is located in
|
||||
// section 4.3 of the specification.
|
||||
repeated uint32 geometry = 4 [ packed = true ];
|
||||
}
|
||||
|
||||
// Layers are described in section 4.1 of the specification
|
||||
message Layer {
|
||||
// Any compliant implementation must first read the version
|
||||
// number encoded in this message and choose the correct
|
||||
// implementation for this version number before proceeding to
|
||||
// decode other parts of this message.
|
||||
required uint32 version = 15 [ default = 1 ];
|
||||
|
||||
required string name = 1;
|
||||
|
||||
// The actual features in this tile.
|
||||
repeated Feature features = 2;
|
||||
|
||||
// Dictionary encoding for keys
|
||||
repeated string keys = 3;
|
||||
|
||||
// Dictionary encoding for values
|
||||
repeated Value values = 4;
|
||||
|
||||
// Although this is an "optional" field it is required by the specification.
|
||||
// See https://github.com/mapbox/vector-tile-spec/issues/47
|
||||
optional uint32 extent = 5 [ default = 4096 ];
|
||||
|
||||
extensions 16 to max;
|
||||
}
|
||||
|
||||
repeated Layer layers = 3;
|
||||
|
||||
extensions 16 to 8191;
|
||||
}
|
||||
4
rustfmt.toml
Normal file
4
rustfmt.toml
Normal file
@ -0,0 +1,4 @@
|
||||
fn_args_layout = "Tall"
|
||||
hard_tabs = false
|
||||
max_width = 80
|
||||
use_field_init_shorthand = true
|
||||
377
src/encoder.rs
Normal file
377
src/encoder.rs
Normal file
@ -0,0 +1,377 @@
|
||||
// encoder.rs
|
||||
//
|
||||
// Copyright (c) 2019-2021 Minnesota Department of Transportation
|
||||
//
|
||||
//! Encoder for Mapbox Vector Tile (MVT) geometry.
|
||||
//!
|
||||
use crate::error::{Error, Result};
|
||||
use pointy::{Float, Transform};
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
enum Command {
|
||||
MoveTo = 1,
|
||||
LineTo = 2,
|
||||
ClosePath = 7,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
struct CommandInt {
|
||||
id: Command,
|
||||
count: u32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
struct ParamInt {
|
||||
value: i32,
|
||||
}
|
||||
|
||||
/// Geometry types for [Features](struct.Feature.html).
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum GeomType {
|
||||
/// Point or multipoint
|
||||
Point,
|
||||
|
||||
/// Linestring or Multilinestring
|
||||
Linestring,
|
||||
|
||||
/// Polygon or Multipolygon
|
||||
Polygon,
|
||||
}
|
||||
|
||||
/// Encoder for [Feature](struct.Feature.html) geometry.
|
||||
///
|
||||
/// This can consist of Point, Linestring or Polygon data.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use mvt::{Error, GeomEncoder, GeomType};
|
||||
/// # use pointy::Transform;
|
||||
/// # fn main() -> Result<(), Error> {
|
||||
/// let geom_data = GeomEncoder::new(GeomType::Point, Transform::default())
|
||||
/// .point(0.0, 0.0)?
|
||||
/// .point(10.0, 0.0)?
|
||||
/// .encode()?;
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub struct GeomEncoder<F>
|
||||
where
|
||||
F: Float,
|
||||
{
|
||||
geom_tp: GeomType,
|
||||
transform: Transform<F>,
|
||||
x: i32,
|
||||
y: i32,
|
||||
cmd_offset: usize,
|
||||
count: u32,
|
||||
data: Vec<u32>,
|
||||
}
|
||||
|
||||
/// Validated geometry data for [Feature](struct.Feature.html)s.
|
||||
///
|
||||
/// Use [GeomEncoder](struct.GeomEncoder.html) to encode.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use mvt::{Error, GeomEncoder, GeomType};
|
||||
/// # use pointy::Transform;
|
||||
/// # fn main() -> Result<(), Error> {
|
||||
/// let geom_data = GeomEncoder::new(GeomType::Point, Transform::default())
|
||||
/// .point(0.0, 0.0)?
|
||||
/// .point(10.0, 0.0)?
|
||||
/// .encode()?;
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub struct GeomData {
|
||||
geom_tp: GeomType,
|
||||
data: Vec<u32>,
|
||||
}
|
||||
|
||||
impl CommandInt {
|
||||
fn new(id: Command, count: u32) -> Self {
|
||||
CommandInt { id, count }
|
||||
}
|
||||
|
||||
fn encode(&self) -> u32 {
|
||||
((self.id as u32) & 0x7) | (self.count << 3)
|
||||
}
|
||||
}
|
||||
|
||||
impl ParamInt {
|
||||
fn new(value: i32) -> Self {
|
||||
ParamInt { value }
|
||||
}
|
||||
|
||||
fn encode(&self) -> u32 {
|
||||
((self.value << 1) ^ (self.value >> 31)) as u32
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> GeomEncoder<F>
|
||||
where
|
||||
F: Float,
|
||||
{
|
||||
/// Create a new geometry encoder.
|
||||
///
|
||||
/// * `geom_tp` Geometry type.
|
||||
/// * `transform` Transform to apply to geometry.
|
||||
pub fn new(geom_tp: GeomType, transform: Transform<F>) -> Self {
|
||||
GeomEncoder {
|
||||
geom_tp,
|
||||
transform,
|
||||
x: 0,
|
||||
y: 0,
|
||||
count: 0,
|
||||
cmd_offset: 0,
|
||||
data: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a Command
|
||||
fn command(&mut self, cmd: Command, count: u32) {
|
||||
self.cmd_offset = self.data.len();
|
||||
debug!("command: {:?}", &cmd);
|
||||
self.data.push(CommandInt::new(cmd, count).encode());
|
||||
}
|
||||
|
||||
/// Set count of the most recent Command.
|
||||
fn set_command(&mut self, cmd: Command, count: u32) {
|
||||
let off = self.cmd_offset;
|
||||
self.data[off] = CommandInt::new(cmd, count).encode();
|
||||
}
|
||||
|
||||
/// Push one point with relative coörindates.
|
||||
fn push_point(&mut self, x: F, y: F) -> Result<()> {
|
||||
let p = self.transform * (x, y);
|
||||
let x = p.x().round().to_i32().ok_or(Error::InvalidValue())?;
|
||||
let y = p.y().round().to_i32().ok_or(Error::InvalidValue())?;
|
||||
self.data
|
||||
.push(ParamInt::new(x.saturating_sub(self.x)).encode());
|
||||
self.data
|
||||
.push(ParamInt::new(y.saturating_sub(self.y)).encode());
|
||||
debug!("point: {},{}", x, y);
|
||||
self.x = x;
|
||||
self.y = y;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add a point.
|
||||
pub fn add_point(&mut self, x: F, y: F) -> Result<()> {
|
||||
match self.geom_tp {
|
||||
GeomType::Point => {
|
||||
if self.count == 0 {
|
||||
self.command(Command::MoveTo, 1);
|
||||
}
|
||||
}
|
||||
GeomType::Linestring => match self.count {
|
||||
0 => self.command(Command::MoveTo, 1),
|
||||
1 => self.command(Command::LineTo, 1),
|
||||
_ => (),
|
||||
},
|
||||
GeomType::Polygon => match self.count {
|
||||
0 => self.command(Command::MoveTo, 1),
|
||||
1 => self.command(Command::LineTo, 1),
|
||||
_ => (),
|
||||
},
|
||||
}
|
||||
self.push_point(x, y)?;
|
||||
self.count += 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add a point, taking ownership (for method chaining).
|
||||
pub fn point(mut self, x: F, y: F) -> Result<Self> {
|
||||
self.add_point(x, y)?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Complete the current geometry (for multilinestring / multipolygon).
|
||||
pub fn complete_geom(&mut self) -> Result<()> {
|
||||
// FIXME: return Error::InvalidGeometry
|
||||
// if "MUST" rules in the spec are violated
|
||||
match self.geom_tp {
|
||||
GeomType::Point => (),
|
||||
GeomType::Linestring => {
|
||||
if self.count > 1 {
|
||||
self.set_command(Command::LineTo, self.count - 1);
|
||||
}
|
||||
self.count = 0;
|
||||
}
|
||||
GeomType::Polygon => {
|
||||
if self.count > 1 {
|
||||
self.set_command(Command::LineTo, self.count - 1);
|
||||
self.command(Command::ClosePath, 1);
|
||||
}
|
||||
self.count = 0;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Complete the current geometry (for multilinestring / multipolygon).
|
||||
pub fn complete(mut self) -> Result<Self> {
|
||||
self.complete_geom()?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Encode the geometry data, consuming the encoder.
|
||||
pub fn encode(mut self) -> Result<GeomData> {
|
||||
// FIXME: return Error::InvalidGeometry
|
||||
// if "MUST" rules in the spec are violated
|
||||
self = if let GeomType::Point = self.geom_tp {
|
||||
if self.count > 1 {
|
||||
self.set_command(Command::MoveTo, self.count);
|
||||
}
|
||||
self
|
||||
} else {
|
||||
self.complete()?
|
||||
};
|
||||
Ok(GeomData::new(self.geom_tp, self.data))
|
||||
}
|
||||
}
|
||||
|
||||
impl GeomData {
|
||||
/// Create new geometry data.
|
||||
///
|
||||
/// * `geom_tp` Geometry type.
|
||||
/// * `data` Validated geometry.
|
||||
fn new(geom_tp: GeomType, data: Vec<u32>) -> Self {
|
||||
GeomData { geom_tp, data }
|
||||
}
|
||||
|
||||
/// Get the geometry type
|
||||
pub(crate) fn geom_type(&self) -> GeomType {
|
||||
self.geom_tp
|
||||
}
|
||||
|
||||
/// Get the geometry data
|
||||
pub(crate) fn into_vec(self) -> Vec<u32> {
|
||||
self.data
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
// Examples from MVT spec:
|
||||
#[test]
|
||||
fn test_point() {
|
||||
let v = GeomEncoder::new(GeomType::Point, Transform::default())
|
||||
.point(25.0, 17.0)
|
||||
.unwrap()
|
||||
.encode()
|
||||
.unwrap()
|
||||
.into_vec();
|
||||
assert_eq!(v, vec!(9, 50, 34));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multipoint() {
|
||||
let v = GeomEncoder::new(GeomType::Point, Transform::default())
|
||||
.point(5.0, 7.0)
|
||||
.unwrap()
|
||||
.point(3.0, 2.0)
|
||||
.unwrap()
|
||||
.encode()
|
||||
.unwrap()
|
||||
.into_vec();
|
||||
assert_eq!(v, vec!(17, 10, 14, 3, 9));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_linestring() {
|
||||
let v = GeomEncoder::new(GeomType::Linestring, Transform::default())
|
||||
.point(2.0, 2.0)
|
||||
.unwrap()
|
||||
.point(2.0, 10.0)
|
||||
.unwrap()
|
||||
.point(10.0, 10.0)
|
||||
.unwrap()
|
||||
.encode()
|
||||
.unwrap()
|
||||
.into_vec();
|
||||
assert_eq!(v, vec!(9, 4, 4, 18, 0, 16, 16, 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multilinestring() {
|
||||
let v = GeomEncoder::new(GeomType::Linestring, Transform::default())
|
||||
.point(2.0, 2.0)
|
||||
.unwrap()
|
||||
.point(2.0, 10.0)
|
||||
.unwrap()
|
||||
.point(10.0, 10.0)
|
||||
.unwrap()
|
||||
.complete()
|
||||
.unwrap()
|
||||
.point(1.0, 1.0)
|
||||
.unwrap()
|
||||
.point(3.0, 5.0)
|
||||
.unwrap()
|
||||
.encode()
|
||||
.unwrap()
|
||||
.into_vec();
|
||||
assert_eq!(v, vec!(9, 4, 4, 18, 0, 16, 16, 0, 9, 17, 17, 10, 4, 8));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_polygon() {
|
||||
let v = GeomEncoder::new(GeomType::Polygon, Transform::default())
|
||||
.point(3.0, 6.0)
|
||||
.unwrap()
|
||||
.point(8.0, 12.0)
|
||||
.unwrap()
|
||||
.point(20.0, 34.0)
|
||||
.unwrap()
|
||||
.encode()
|
||||
.unwrap()
|
||||
.into_vec();
|
||||
assert_eq!(v, vec!(9, 6, 12, 18, 10, 12, 24, 44, 15));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multipolygon() {
|
||||
let v = GeomEncoder::new(GeomType::Polygon, Transform::default())
|
||||
// positive area => exterior ring
|
||||
.point(0.0, 0.0)
|
||||
.unwrap()
|
||||
.point(10.0, 0.0)
|
||||
.unwrap()
|
||||
.point(10.0, 10.0)
|
||||
.unwrap()
|
||||
.point(0.0, 10.0)
|
||||
.unwrap()
|
||||
.complete()
|
||||
.unwrap()
|
||||
// positive area => exterior ring
|
||||
.point(11.0, 11.0)
|
||||
.unwrap()
|
||||
.point(20.0, 11.0)
|
||||
.unwrap()
|
||||
.point(20.0, 20.0)
|
||||
.unwrap()
|
||||
.point(11.0, 20.0)
|
||||
.unwrap()
|
||||
.complete()
|
||||
.unwrap()
|
||||
// negative area => interior ring
|
||||
.point(13.0, 13.0)
|
||||
.unwrap()
|
||||
.point(13.0, 17.0)
|
||||
.unwrap()
|
||||
.point(17.0, 17.0)
|
||||
.unwrap()
|
||||
.point(17.0, 13.0)
|
||||
.unwrap()
|
||||
.encode()
|
||||
.unwrap()
|
||||
.into_vec();
|
||||
assert_eq!(
|
||||
v,
|
||||
vec!(
|
||||
9, 0, 0, 26, 20, 0, 0, 20, 19, 0, 15, 9, 22, 2, 26, 18, 0, 0,
|
||||
18, 17, 0, 15, 9, 4, 13, 26, 0, 8, 8, 0, 0, 7, 15
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
37
src/error.rs
Normal file
37
src/error.rs
Normal file
@ -0,0 +1,37 @@
|
||||
// error.rs
|
||||
//
|
||||
// Copyright (c) 2019-2021 Minnesota Department of Transportation
|
||||
//
|
||||
use protobuf::error::ProtobufError;
|
||||
|
||||
/// MVT Error types
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
/// The tile already contains a layer with the specified name.
|
||||
#[error("Duplicate name")]
|
||||
DuplicateName(),
|
||||
|
||||
/// The layer extent does not match the tile extent.
|
||||
#[error("Wrong layer extent")]
|
||||
WrongExtent(),
|
||||
|
||||
/// The tile ID is invalid.
|
||||
#[error("Invalid tile ID")]
|
||||
InvalidTid(),
|
||||
|
||||
/// The geometry does not meet criteria of the specification.
|
||||
#[error("Invalid geometry data")]
|
||||
InvalidGeometry(),
|
||||
|
||||
/// Invalid float value
|
||||
#[error("Invalid float value")]
|
||||
InvalidValue(),
|
||||
|
||||
/// Error while encoding protobuf data.
|
||||
#[error("Protobuf error {0}")]
|
||||
Protobuf(#[from] ProtobufError),
|
||||
}
|
||||
|
||||
/// MVT Result
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
57
src/lib.rs
Normal file
57
src/lib.rs
Normal file
@ -0,0 +1,57 @@
|
||||
// lib.rs mvt crate.
|
||||
//
|
||||
// Copyright (c) 2019-2021 Minnesota Department of Transportation
|
||||
//
|
||||
//! A library for encoding [mapbox vector tiles].
|
||||
//!
|
||||
//! A [tile] is composed of one or more [layer]s. Each layer can have any number
|
||||
//! of [feature]s, which contain the geometry to be rendered. They can also have
|
||||
//! metadata tags, which are key/value pairs.
|
||||
//!
|
||||
//! ## Example
|
||||
//!
|
||||
//! ```rust
|
||||
//! use mvt::{Error, GeomEncoder, GeomType, Tile};
|
||||
//! use pointy::Transform;
|
||||
//!
|
||||
//! fn main() -> Result<(), Error> {
|
||||
//! let mut tile = Tile::new(4096);
|
||||
//! let layer = tile.create_layer("First Layer");
|
||||
//! // NOTE: normally, the Transform would come from MapGrid::tile_transform
|
||||
//! let b = GeomEncoder::new(GeomType::Linestring, Transform::default())
|
||||
//! .point(0.0, 0.0)?
|
||||
//! .point(1024.0, 0.0)?
|
||||
//! .point(1024.0, 2048.0)?
|
||||
//! .point(2048.0, 2048.0)?
|
||||
//! .point(2048.0, 4096.0)?
|
||||
//! .encode()?;
|
||||
//! let mut feature = layer.into_feature(b);
|
||||
//! feature.set_id(1);
|
||||
//! feature.add_tag_string("key", "value");
|
||||
//! let layer = feature.into_layer();
|
||||
//! tile.add_layer(layer)?;
|
||||
//! let data = tile.to_bytes()?;
|
||||
//! println!("encoded {} bytes: {:?}", data.len(), data);
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! [feature]: struct.Feature.html
|
||||
//! [layer]: struct.Layer.html
|
||||
//! [mapbox vector tiles]: https://github.com/mapbox/vector-tile-spec
|
||||
//! [tile]: struct.Tile.html
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
mod encoder;
|
||||
mod error;
|
||||
mod mapgrid;
|
||||
mod tile;
|
||||
mod vector_tile;
|
||||
|
||||
pub use crate::encoder::{GeomData, GeomEncoder, GeomType};
|
||||
pub use crate::error::Error;
|
||||
pub use crate::mapgrid::{MapGrid, TileId};
|
||||
pub use crate::tile::{Feature, Layer, Tile};
|
||||
11
src/main.rs
Normal file
11
src/main.rs
Normal file
@ -0,0 +1,11 @@
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
// Update vector tile module to new protobuf version
|
||||
fn main() {
|
||||
protobuf_codegen_pure::Codegen::new()
|
||||
.out_dir("src/")
|
||||
.inputs(&["protos/vector_tile.proto"])
|
||||
.include("protos")
|
||||
.run()
|
||||
.expect("Codegen failed");
|
||||
}
|
||||
254
src/mapgrid.rs
Normal file
254
src/mapgrid.rs
Normal file
@ -0,0 +1,254 @@
|
||||
// mapgrid.rs
|
||||
//
|
||||
// Copyright (c) 2019-2021 Minnesota Department of Transportation
|
||||
//
|
||||
//! TileId and MapGrid structs.
|
||||
//!
|
||||
use crate::error::{Error, Result};
|
||||
use num_traits::FromPrimitive;
|
||||
use pointy::{BBox, Float, Pt, Transform};
|
||||
use std::fmt;
|
||||
|
||||
/// Web Mercator map constants
|
||||
pub trait MapConst {
|
||||
/// Half size of map (meters)
|
||||
const HALF_SIZE_M: Self;
|
||||
}
|
||||
|
||||
impl MapConst for f32 {
|
||||
const HALF_SIZE_M: Self = 20_037_508.342_789_248;
|
||||
}
|
||||
|
||||
impl MapConst for f64 {
|
||||
const HALF_SIZE_M: Self = 20_037_508.342_789_248;
|
||||
}
|
||||
|
||||
/// A tile ID identifies a tile on a map grid at a specific zoom level.
|
||||
///
|
||||
/// It uses XYZ addressing, with X increasing from west to east and Y increasing
|
||||
/// from north to south. The X and Y values can range from 0 to
|
||||
/// 2<sup>Z</sup>-1.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct TileId {
|
||||
x: u32, // not public to prevent invalid values being created
|
||||
y: u32,
|
||||
z: u32,
|
||||
}
|
||||
|
||||
/// A map grid is used to address [tile]s on a map.
|
||||
///
|
||||
/// The grid should be in projected coördinates. Use `default()` for
|
||||
/// [Web Mercator].
|
||||
///
|
||||
/// [tile]: struct.Tile.html
|
||||
/// [Web Mercator]: https://en.wikipedia.org/wiki/Web_Mercator_projection
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MapGrid<F>
|
||||
where
|
||||
F: Float,
|
||||
{
|
||||
/// Spatial reference ID
|
||||
srid: i32,
|
||||
|
||||
/// Bounding box
|
||||
bbox: BBox<F>,
|
||||
}
|
||||
|
||||
impl TileId {
|
||||
/// Get the X value.
|
||||
pub fn x(&self) -> u32 {
|
||||
self.x
|
||||
}
|
||||
|
||||
/// Get the Y value.
|
||||
pub fn y(&self) -> u32 {
|
||||
self.y
|
||||
}
|
||||
|
||||
/// Get the Z (zoom) value.
|
||||
pub fn z(&self) -> u32 {
|
||||
self.z
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for TileId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}/{}/{}", self.z, self.x, self.y)
|
||||
}
|
||||
}
|
||||
|
||||
impl TileId {
|
||||
/// Create a new TildId.
|
||||
///
|
||||
/// If invalid, returns [Error::InvalidTid](enum.Error.html).
|
||||
pub fn new(x: u32, y: u32, z: u32) -> Result<Self> {
|
||||
TileId::check_valid(x, y, z)?;
|
||||
Ok(TileId { x, y, z })
|
||||
}
|
||||
|
||||
/// Check whether a tile ID is valid.
|
||||
fn check_valid(x: u32, y: u32, z: u32) -> Result<()> {
|
||||
if z > 31 {
|
||||
return Err(Error::InvalidTid());
|
||||
}
|
||||
let s = 1 << z;
|
||||
if x < s && y < s {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::InvalidTid())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> Default for MapGrid<F>
|
||||
where
|
||||
F: Float + MapConst,
|
||||
{
|
||||
fn default() -> Self {
|
||||
const WEB_MERCATOR_SRID: i32 = 3857;
|
||||
let srid = WEB_MERCATOR_SRID;
|
||||
let p0 = Pt::new(-F::HALF_SIZE_M, -F::HALF_SIZE_M);
|
||||
let p1 = Pt::new(F::HALF_SIZE_M, F::HALF_SIZE_M);
|
||||
let bbox = BBox::from((p0, p1));
|
||||
Self { srid, bbox }
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> MapGrid<F>
|
||||
where
|
||||
F: Float + FromPrimitive,
|
||||
{
|
||||
/// Create a new map grid.
|
||||
///
|
||||
/// * `srid` Spatial reference ID.
|
||||
/// * `bbox` Bounding box.
|
||||
pub fn new(srid: i32, bbox: BBox<F>) -> Self {
|
||||
MapGrid { srid, bbox }
|
||||
}
|
||||
|
||||
/// Get the spatial reference ID.
|
||||
pub fn srid(&self) -> i32 {
|
||||
self.srid
|
||||
}
|
||||
|
||||
/// Get the bounding box of the grid.
|
||||
pub fn bbox(&self) -> BBox<F> {
|
||||
self.bbox
|
||||
}
|
||||
|
||||
/// Get the bounding box of a tile ID.
|
||||
pub fn tile_bbox(&self, tid: TileId) -> BBox<F> {
|
||||
let tx = self.bbox.x_min(); // west edge
|
||||
let ty = self.bbox.y_max(); // north edge
|
||||
let tz = zoom_scale(tid.z);
|
||||
let sx = self.bbox.x_span() * tz;
|
||||
let sy = self.bbox.y_span() * tz;
|
||||
let t = Transform::with_scale(sx, -sy).translate(tx, ty);
|
||||
let tidx = F::from_u32(tid.x).unwrap();
|
||||
let tidy = F::from_u32(tid.y).unwrap();
|
||||
let p0 = t * Pt::new(tidx, tidy);
|
||||
let p1 = t * Pt::new(tidx + F::one(), tidy + F::one());
|
||||
BBox::from((p0, p1))
|
||||
}
|
||||
|
||||
/// Get the transform to coördinates in 0 to 1 range.
|
||||
pub fn tile_transform(&self, tid: TileId) -> Transform<F> {
|
||||
let tx = self.bbox.x_min(); // west edge
|
||||
let ty = self.bbox.y_max(); // north edge
|
||||
let tz = F::from_u32(1 << tid.z).unwrap();
|
||||
let sx = tz / self.bbox.x_span();
|
||||
let sy = tz / self.bbox.y_span();
|
||||
let tidx = F::from_u32(tid.x).unwrap();
|
||||
let tidy = F::from_u32(tid.y).unwrap();
|
||||
Transform::with_translate(-tx, -ty)
|
||||
.scale(sx, -sy)
|
||||
.translate(-tidx, -tidy)
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate scales at one zoom level.
|
||||
fn zoom_scale<F>(zoom: u32) -> F
|
||||
where
|
||||
F: Float + FromPrimitive,
|
||||
{
|
||||
F::one() / F::from_u32(1 << zoom).unwrap()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_tile_bbox() {
|
||||
let g = MapGrid::<f64>::default();
|
||||
let tid = TileId::new(0, 0, 0).unwrap();
|
||||
let b = g.tile_bbox(tid);
|
||||
assert_eq!(b.x_min(), -20037508.3427892480);
|
||||
assert_eq!(b.x_max(), 20037508.3427892480);
|
||||
assert_eq!(b.y_min(), -20037508.3427892480);
|
||||
assert_eq!(b.y_max(), 20037508.3427892480);
|
||||
|
||||
let tid = TileId::new(0, 0, 1).unwrap();
|
||||
let b = g.tile_bbox(tid);
|
||||
assert_eq!(b.x_min(), -20037508.3427892480);
|
||||
assert_eq!(b.x_max(), 0.0);
|
||||
assert_eq!(b.y_min(), 0.0);
|
||||
assert_eq!(b.y_max(), 20037508.3427892480);
|
||||
|
||||
let tid = TileId::new(1, 1, 1).unwrap();
|
||||
let b = g.tile_bbox(tid);
|
||||
assert_eq!(b.x_min(), 0.0);
|
||||
assert_eq!(b.x_max(), 20037508.3427892480);
|
||||
assert_eq!(b.y_min(), -20037508.3427892480);
|
||||
assert_eq!(b.y_max(), 0.0);
|
||||
|
||||
let tid = TileId::new(246, 368, 10).unwrap();
|
||||
let b = g.tile_bbox(tid);
|
||||
assert_eq!(b.x_min(), -10410111.756214727);
|
||||
assert_eq!(b.x_max(), -10370975.997732716);
|
||||
assert_eq!(b.y_min(), 5596413.462927466);
|
||||
assert_eq!(b.y_max(), 5635549.221409475);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tile_transform() {
|
||||
let g = MapGrid::default();
|
||||
let tid = TileId::new(0, 0, 0).unwrap();
|
||||
let t = g.tile_transform(tid);
|
||||
assert_eq!(
|
||||
Pt::new(0.0, 0.0),
|
||||
t * Pt::new(-20037508.3427892480, 20037508.3427892480)
|
||||
);
|
||||
assert_eq!(
|
||||
Pt::new(1.0, 1.0),
|
||||
t * Pt::new(20037508.3427892480, -20037508.3427892480)
|
||||
);
|
||||
|
||||
let tid = TileId::new(0, 0, 1).unwrap();
|
||||
let t = g.tile_transform(tid);
|
||||
assert_eq!(
|
||||
Pt::new(0.0, 0.0),
|
||||
t * Pt::new(-20037508.3427892480, 20037508.3427892480)
|
||||
);
|
||||
assert_eq!(Pt::new(1.0, 1.0), t * Pt::new(0.0, 0.0));
|
||||
|
||||
let tid = TileId::new(1, 1, 1).unwrap();
|
||||
let t = g.tile_transform(tid);
|
||||
assert_eq!(Pt::new(0.0, 0.0), t * Pt::new(0.0, 0.0));
|
||||
assert_eq!(
|
||||
Pt::new(1.0, 1.0),
|
||||
t * Pt::new(20037508.3427892480, -20037508.3427892480)
|
||||
);
|
||||
|
||||
let tid = TileId::new(246, 368, 10).unwrap();
|
||||
let t = g.tile_transform(tid);
|
||||
assert_eq!(
|
||||
Pt::new(0.0, 0.0),
|
||||
t * Pt::new(-10410111.756214727, 5635549.221409475)
|
||||
);
|
||||
assert_eq!(
|
||||
Pt::new(1.0, 0.9999999999999716),
|
||||
t * Pt::new(-10370975.997732716, 5596413.462927466)
|
||||
);
|
||||
}
|
||||
}
|
||||
340
src/tile.rs
Normal file
340
src/tile.rs
Normal file
@ -0,0 +1,340 @@
|
||||
// tile.rs
|
||||
//
|
||||
// Copyright (c) 2019-2021 Minnesota Department of Transportation
|
||||
//
|
||||
//! Tile, Layer and Feature structs.
|
||||
//!
|
||||
use crate::encoder::{GeomData, GeomType};
|
||||
use crate::error::{Error, Result};
|
||||
use crate::vector_tile::Tile as VecTile;
|
||||
use crate::vector_tile::{Tile_Feature, Tile_GeomType, Tile_Layer, Tile_Value};
|
||||
use protobuf::{CodedOutputStream, Message};
|
||||
use std::io::Write;
|
||||
|
||||
/// A tile represents a rectangular region of a map.
|
||||
///
|
||||
/// Each tile can contain any number of [layers]. When all layers have been
|
||||
/// added to the tile, it can be [written out] or [converted] to a `Vec<u8>`.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use mvt::Error;
|
||||
/// # fn main() -> Result<(), Error> {
|
||||
/// use mvt::Tile;
|
||||
///
|
||||
/// let mut tile = Tile::new(4096);
|
||||
/// let layer = tile.create_layer("First Layer");
|
||||
/// // ...
|
||||
/// // set up the layer
|
||||
/// // ...
|
||||
/// tile.add_layer(layer)?;
|
||||
/// // ...
|
||||
/// // add more layers
|
||||
/// // ...
|
||||
/// let data = tile.to_bytes()?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [converted]: struct.Tile.html#method.to_bytes
|
||||
/// [layers]: struct.Layer.html
|
||||
/// [written out]: struct.Tile.html#method.write_to
|
||||
pub struct Tile {
|
||||
vec_tile: VecTile,
|
||||
extent: u32,
|
||||
}
|
||||
|
||||
/// A layer is a set of related features in a tile.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use mvt::Tile;
|
||||
///
|
||||
/// let mut tile = Tile::new(4096);
|
||||
/// let layer = tile.create_layer("First Layer");
|
||||
/// // ...
|
||||
/// // set up the layer
|
||||
/// // ...
|
||||
/// ```
|
||||
pub struct Layer {
|
||||
layer: Tile_Layer,
|
||||
}
|
||||
|
||||
/// A Feature contains map geometry with related metadata.
|
||||
///
|
||||
/// A new Feature can be obtained with [Layer.into_feature].
|
||||
/// After optionally adding an ID and tags, retrieve the Layer with the Feature
|
||||
/// added by calling [Feature.into_layer].
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use mvt::Error;
|
||||
/// # fn main() -> Result<(), Error> {
|
||||
/// use mvt::{GeomEncoder, GeomType, Tile};
|
||||
/// use pointy::Transform;
|
||||
///
|
||||
/// let tile = Tile::new(4096);
|
||||
/// let layer = tile.create_layer("First Layer");
|
||||
/// let geom_data = GeomEncoder::new(GeomType::Point, Transform::default())
|
||||
/// .point(1.0, 2.0)?
|
||||
/// .point(7.0, 6.0)?
|
||||
/// .encode()?;
|
||||
/// let feature = layer.into_feature(geom_data);
|
||||
/// // ...
|
||||
/// // add any tags or ID to the feature
|
||||
/// // ...
|
||||
/// let layer = feature.into_layer();
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [Layer.into_feature]: struct.Layer.html#method.into_feature
|
||||
/// [Feature.into_layer]: struct.Feature.html#method.into_layer
|
||||
pub struct Feature {
|
||||
feature: Tile_Feature,
|
||||
layer: Layer,
|
||||
num_keys: usize,
|
||||
num_values: usize,
|
||||
}
|
||||
|
||||
impl Tile {
|
||||
/// Create a new tile.
|
||||
///
|
||||
/// * `extent` Height / width of tile bounds.
|
||||
pub fn new(extent: u32) -> Self {
|
||||
let vec_tile = VecTile::new();
|
||||
Tile { vec_tile, extent }
|
||||
}
|
||||
|
||||
/// Get extent, or height / width of tile bounds.
|
||||
pub fn extent(&self) -> u32 {
|
||||
self.extent
|
||||
}
|
||||
|
||||
/// Get the number of layers.
|
||||
pub fn num_layers(&self) -> usize {
|
||||
self.vec_tile.get_layers().len()
|
||||
}
|
||||
|
||||
/// Create a new layer.
|
||||
///
|
||||
/// * `name` Layer name.
|
||||
pub fn create_layer(&self, name: &str) -> Layer {
|
||||
Layer::new(name, self.extent)
|
||||
}
|
||||
|
||||
/// Add a layer.
|
||||
///
|
||||
/// * `layer` The layer.
|
||||
///
|
||||
/// Returns an error if:
|
||||
/// * a layer with the same name already exists
|
||||
/// * the layer extent does not match the tile extent
|
||||
pub fn add_layer(&mut self, layer: Layer) -> Result<()> {
|
||||
if layer.layer.get_extent() != self.extent {
|
||||
return Err(Error::WrongExtent());
|
||||
}
|
||||
if self
|
||||
.vec_tile
|
||||
.get_layers()
|
||||
.iter()
|
||||
.any(|n| n.get_name() == layer.layer.get_name())
|
||||
{
|
||||
Err(Error::DuplicateName())
|
||||
} else {
|
||||
self.vec_tile.mut_layers().push(layer.layer);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Write the tile.
|
||||
///
|
||||
/// * `out` Writer to output the tile.
|
||||
pub fn write_to(&self, mut out: &mut dyn Write) -> Result<()> {
|
||||
let mut os = CodedOutputStream::new(&mut out);
|
||||
let _ = self.vec_tile.write_to(&mut os);
|
||||
os.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Encode the tile and return the bytes.
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>> {
|
||||
let mut v = Vec::with_capacity(self.compute_size());
|
||||
self.write_to(&mut v)?;
|
||||
Ok(v)
|
||||
}
|
||||
|
||||
/// Compute the encoded size in bytes.
|
||||
pub fn compute_size(&self) -> usize {
|
||||
self.vec_tile.compute_size() as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Layer {
|
||||
fn default() -> Self {
|
||||
let layer = Tile_Layer::new();
|
||||
Layer { layer }
|
||||
}
|
||||
}
|
||||
|
||||
impl Layer {
|
||||
/// Create a new layer.
|
||||
///
|
||||
/// * `name` Layer name.
|
||||
/// * `extent` Width / height of tile bounds.
|
||||
fn new(name: &str, extent: u32) -> Self {
|
||||
let mut layer = Tile_Layer::new();
|
||||
layer.set_version(2);
|
||||
layer.set_name(name.to_string());
|
||||
layer.set_extent(extent);
|
||||
Layer { layer }
|
||||
}
|
||||
|
||||
/// Get the layer name.
|
||||
pub fn name(&self) -> &str {
|
||||
self.layer.get_name()
|
||||
}
|
||||
|
||||
/// Get number of features (count).
|
||||
pub fn num_features(&self) -> usize {
|
||||
self.layer.get_features().len()
|
||||
}
|
||||
|
||||
/// Create a new feature, giving it ownership of the layer.
|
||||
///
|
||||
/// * `geom_data` Geometry data (consumed by this method).
|
||||
pub fn into_feature(self, geom_data: GeomData) -> Feature {
|
||||
let num_keys = self.layer.get_keys().len();
|
||||
let num_values = self.layer.get_values().len();
|
||||
let mut feature = Tile_Feature::new();
|
||||
feature.set_field_type(match geom_data.geom_type() {
|
||||
GeomType::Point => Tile_GeomType::POINT,
|
||||
GeomType::Linestring => Tile_GeomType::LINESTRING,
|
||||
GeomType::Polygon => Tile_GeomType::POLYGON,
|
||||
});
|
||||
feature.set_geometry(geom_data.into_vec());
|
||||
Feature {
|
||||
feature,
|
||||
layer: self,
|
||||
num_keys,
|
||||
num_values,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get position of a key in the layer keys. If the key is not found, it
|
||||
/// is added as the last key.
|
||||
fn key_pos(&mut self, key: &str) -> usize {
|
||||
self.layer
|
||||
.get_keys()
|
||||
.iter()
|
||||
.position(|k| *k == key)
|
||||
.unwrap_or_else(|| {
|
||||
self.layer.mut_keys().push(key.to_string());
|
||||
self.layer.get_keys().len() - 1
|
||||
})
|
||||
}
|
||||
|
||||
/// Get position of a value in the layer values. If the value is not found,
|
||||
/// it is added as the last value.
|
||||
fn val_pos(&mut self, value: Tile_Value) -> usize {
|
||||
self.layer
|
||||
.get_values()
|
||||
.iter()
|
||||
.position(|v| *v == value)
|
||||
.unwrap_or_else(|| {
|
||||
self.layer.mut_values().push(value);
|
||||
self.layer.get_values().len() - 1
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Feature {
|
||||
/// Complete the feature, returning ownership of the layer.
|
||||
pub fn into_layer(mut self) -> Layer {
|
||||
self.layer.layer.mut_features().push(self.feature);
|
||||
self.layer
|
||||
}
|
||||
|
||||
/// Get the layer, abandoning the feature.
|
||||
pub fn layer(mut self) -> Layer {
|
||||
// Reset key/value lengths
|
||||
self.layer.layer.mut_keys().truncate(self.num_keys);
|
||||
self.layer.layer.mut_values().truncate(self.num_values);
|
||||
self.layer
|
||||
}
|
||||
|
||||
/// Set the feature ID.
|
||||
pub fn set_id(&mut self, id: u64) {
|
||||
let layer = &self.layer.layer;
|
||||
if layer.get_features().iter().any(|f| f.get_id() == id) {
|
||||
warn!(
|
||||
"Duplicate feature ID ({}) in layer {}",
|
||||
id,
|
||||
layer.get_name()
|
||||
);
|
||||
}
|
||||
self.feature.set_id(id);
|
||||
}
|
||||
|
||||
/// Get number of tags (count).
|
||||
pub fn num_tags(&self) -> usize {
|
||||
self.feature.get_tags().len()
|
||||
}
|
||||
|
||||
/// Add a tag of string type.
|
||||
pub fn add_tag_string(&mut self, key: &str, val: &str) {
|
||||
let mut value = Tile_Value::new();
|
||||
value.set_string_value(val.to_string());
|
||||
self.add_tag(key, value);
|
||||
}
|
||||
|
||||
/// Add a tag of double type.
|
||||
pub fn add_tag_double(&mut self, key: &str, val: f64) {
|
||||
let mut value = Tile_Value::new();
|
||||
value.set_double_value(val);
|
||||
self.add_tag(key, value);
|
||||
}
|
||||
|
||||
/// Add a tag of float type.
|
||||
pub fn add_tag_float(&mut self, key: &str, val: f32) {
|
||||
let mut value = Tile_Value::new();
|
||||
value.set_float_value(val);
|
||||
self.add_tag(key, value);
|
||||
}
|
||||
|
||||
/// Add a tag of int type.
|
||||
pub fn add_tag_int(&mut self, key: &str, val: i64) {
|
||||
let mut value = Tile_Value::new();
|
||||
value.set_int_value(val);
|
||||
self.add_tag(key, value);
|
||||
}
|
||||
|
||||
/// Add a tag of uint type.
|
||||
pub fn add_tag_uint(&mut self, key: &str, val: u64) {
|
||||
let mut value = Tile_Value::new();
|
||||
value.set_uint_value(val);
|
||||
self.add_tag(key, value);
|
||||
}
|
||||
|
||||
/// Add a tag of sint type.
|
||||
pub fn add_tag_sint(&mut self, key: &str, val: i64) {
|
||||
let mut value = Tile_Value::new();
|
||||
value.set_sint_value(val);
|
||||
self.add_tag(key, value);
|
||||
}
|
||||
|
||||
/// Add a tag of bool type.
|
||||
pub fn add_tag_bool(&mut self, key: &str, val: bool) {
|
||||
let mut value = Tile_Value::new();
|
||||
value.set_bool_value(val);
|
||||
self.add_tag(key, value);
|
||||
}
|
||||
|
||||
/// Add a tag.
|
||||
fn add_tag(&mut self, key: &str, value: Tile_Value) {
|
||||
let kidx = self.layer.key_pos(key);
|
||||
self.feature.mut_tags().push(kidx as u32);
|
||||
let vidx = self.layer.val_pos(value);
|
||||
self.feature.mut_tags().push(vidx as u32);
|
||||
}
|
||||
}
|
||||
1360
src/vector_tile.rs
Normal file
1360
src/vector_tile.rs
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user