Skip to content

Commit 287d6cc

Browse files
authored
sort geobuf keys, rounding just like js, python (#16)
* sort keys * not ready * export geobuf with ordered keys! * only_xy=false * dim * remove normalize * add release notes * use round * add test
1 parent ad7f13a commit 287d6cc

File tree

9 files changed

+136
-67
lines changed

9 files changed

+136
-67
lines changed

docs/about/release-notes.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ To upgrade `pybind11-geobuf` to the latest version, use pip:
1010
pip install -U pybind11-geobuf
1111
```
1212

13+
## Version 0.0.9 (2023-03-30)
14+
15+
* Export to geobuf with only xy (no z), `only_xy=False`
16+
* Geobuf property keys are sorted now!
17+
1318
## Version 0.0.8 (2023-03-28)
1419

1520
* Integrate geobuf into geojson!

pybind11_geobuf/__main__.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,13 @@ def json2geobuf(
3838
output_path: str,
3939
*,
4040
precision: int = 8,
41+
only_xy: bool = False,
4142
):
4243
logger.info(
4344
f"geobuf encoding {input_path} ({__filesize(input_path):,} bytes)..."
4445
) # noqa
4546
os.makedirs(os.path.dirname(os.path.abspath(output_path)), exist_ok=True)
46-
encoder = Encoder(max_precision=int(10**precision))
47+
encoder = Encoder(max_precision=int(10**precision), only_xy=only_xy)
4748
assert encoder.encode(
4849
geojson=input_path,
4950
geobuf=output_path,
@@ -57,6 +58,7 @@ def normalize_geobuf(
5758
*,
5859
sort_keys: bool = True,
5960
precision: int = -1,
61+
only_xy: bool = False,
6062
):
6163
logger.info(
6264
f"normalize_geobuf {input_path} ({__filesize(input_path):,} bytes)"
@@ -70,7 +72,7 @@ def normalize_geobuf(
7072
logger.info(f"auto precision from geobuf: {precision}")
7173
else:
7274
logger.info(f"user precision: {precision}")
73-
encoder = Encoder(max_precision=int(10**precision))
75+
encoder = Encoder(max_precision=int(10**precision), only_xy=only_xy)
7476
encoded = encoder.encode(json)
7577
logger.info(f"encoded #bytes: {len(encoded):,}")
7678
output_path = output_path or input_path
@@ -87,6 +89,7 @@ def normalize_json(
8789
indent: bool = True,
8890
sort_keys: bool = True,
8991
precision: int = -1,
92+
only_xy: bool = False,
9093
):
9194
logger.info(
9295
f"normalize_json {input_path} ({__filesize(input_path):,} bytes)"
@@ -97,7 +100,7 @@ def normalize_json(
97100
logger.info(
98101
f"convert to geobuf (precision: {precision}), then back to geojson"
99102
) # noqa
100-
encoder = Encoder(max_precision=int(10**precision))
103+
encoder = Encoder(max_precision=int(10**precision), only_xy=only_xy)
101104
geojson = rapidjson().load(input_path)
102105
assert geojson.IsObject(), f"invalid geojson: {input_path}"
103106
encoded = encoder.encode(geojson)

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ def build_extension(self, ext):
127127
# logic and declaration, and simpler if you include description/version in a file.
128128
setup(
129129
name="pybind11_geobuf",
130-
version="0.0.8",
130+
version="0.0.9",
131131
author="tzx",
132132
author_email="dvorak4tzx@gmail.com",
133133
url="https://geobuf-cpp.readthedocs.io",

src/geobuf/geobuf.cpp

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <array>
55
#include <mapbox/geojson_impl.hpp>
66
#include <mapbox/geojson_value_impl.hpp>
7+
#include <set>
78

89
#include "rapidjson/error/en.h"
910
#include "rapidjson/filereadstream.h"
@@ -212,25 +213,27 @@ std::string dump(const mapbox::geojson::geojson &geojson, //
212213

213214
std::string Encoder::encode(const mapbox::geojson::geojson &geojson)
214215
{
216+
std::string data;
217+
Encoder::Pbf pbf{data};
218+
215219
dim = MAPBOX_GEOBUF_DEFAULT_DIM;
216220
e = 1;
217221
keys.clear();
218222
analyze(geojson);
219-
220-
std::vector<std::pair<const std::string *, uint32_t>> keys_vec;
221-
keys_vec.reserve(keys.size());
222-
for (auto &pair : keys) {
223-
keys_vec.emplace_back(&pair.first, pair.second);
223+
if (onlyXY) {
224+
dim = 2u;
224225
}
225-
std::sort(keys_vec.begin(), keys_vec.end(),
226-
[](const auto &kv1, const auto &kv2) {
227-
return kv1.second < kv2.second;
228-
});
229226

230-
std::string data;
231-
Encoder::Pbf pbf{data};
232-
for (auto &kv : keys_vec) {
233-
pbf.add_string(1, *kv.first);
227+
{
228+
auto kk = std::set<std::string>();
229+
for (auto &pair : keys) {
230+
kk.insert(pair.first);
231+
}
232+
int idx = -1;
233+
for (auto &k : kk) {
234+
pbf.add_string(1, k);
235+
keys[k] = ++idx;
236+
}
234237
}
235238
if (dim != MAPBOX_GEOBUF_DEFAULT_DIM) {
236239
pbf.add_uint32(2, dim);
@@ -346,15 +349,23 @@ void Encoder::analyzePoints(const PointsType &points)
346349
}
347350
}
348351

352+
inline double ROUND(double v, double s)
353+
{
354+
return std::floor(v * s + 0.5) / s;
355+
// return std::round(v * s) / s;
356+
}
357+
349358
void Encoder::analyzePoint(const mapbox::geojson::point &point)
350359
{
351-
dim = std::max(point.z == 0 ? dimXY : dimXYZ, dim);
360+
if (!onlyXY) {
361+
dim = std::max(point.z == 0 ? dimXY : dimXYZ, dim);
362+
}
352363
if (e >= maxPrecision) {
353364
return;
354365
}
355366
const double *ptr = &point.x;
356367
for (int i = 0; i < dim; ++i) {
357-
while (std::round(ptr[i] * e) / e != ptr[i] && e < maxPrecision) {
368+
while (ROUND(ptr[i], e) != ptr[i] && e < maxPrecision) {
358369
e *= 10;
359370
}
360371
}
@@ -502,7 +513,7 @@ void Encoder::writePoint(const mapbox::geojson::point &point, Encoder::Pbf &pbf)
502513
coords.reserve(dim);
503514
const double *ptr = &point.x;
504515
for (int i = 0; i < dim; ++i) {
505-
coords.push_back(static_cast<int64_t>(std::round(ptr[i] * e)));
516+
coords.push_back(static_cast<int64_t>(std::floor(ptr[i] * e + 0.5)));
506517
}
507518
pbf.add_packed_sint64(3, coords.begin(), coords.end());
508519
}
@@ -570,7 +581,8 @@ void Encoder::populateLine(std::vector<int64_t> &coords, //
570581
for (int i = 0; i < len; ++i) {
571582
const double *ptr = &line[i].x;
572583
for (int j = 0; j < dim; ++j) {
573-
auto n = static_cast<int64_t>(std::round(ptr[j] * e)) - sum[j];
584+
auto n =
585+
static_cast<int64_t>(std::floor(ptr[j] * e + 0.5)) - sum[j];
574586
coords.push_back(n);
575587
sum[j] += n;
576588
}

src/geobuf/geobuf.hpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,9 @@ struct Encoder
8080
{
8181
using Pbf = protozero::pbf_writer;
8282
Encoder(uint32_t maxPrecision = std::pow(10,
83-
MAPBOX_GEOBUF_DEFAULT_PRECISION))
84-
: maxPrecision(maxPrecision)
83+
MAPBOX_GEOBUF_DEFAULT_PRECISION),
84+
bool onlyXY = false)
85+
: maxPrecision(maxPrecision), onlyXY(onlyXY)
8586
{
8687
}
8788
std::string encode(const mapbox::geojson::geojson &geojson);
@@ -138,6 +139,7 @@ struct Encoder
138139
bool closed);
139140

140141
const uint32_t maxPrecision;
142+
const bool onlyXY;
141143
uint32_t dim = MAPBOX_GEOBUF_DEFAULT_DIM;
142144
uint32_t e = 1;
143145
std::unordered_map<std::string, std::uint32_t> keys;
@@ -153,6 +155,8 @@ struct Decoder
153155
bool decode(const std::string &input_path, const std::string &output_path,
154156
bool indent = false, bool sort_keys = false);
155157
int precision() const { return std::log10(e); }
158+
int __dim() const { return dim; }
159+
std::vector<std::string> __keys() const { return keys; }
156160

157161
private:
158162
mapbox::geojson::feature_collection readFeatureCollection(Pbf &pbf);

src/geobuf/geojson_helpers.hpp

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -451,10 +451,8 @@ inline double round_coords(double value, double scale)
451451
// also note that, javascript Math.round differs from C++ round
452452
// e.g. std::round(-0.5) => -1.0
453453
// Math.round(-0.5) => -0.0
454-
return std::round(value * scale) / scale;
455-
// TODO, all should use Math.round!!!
456-
// Math.round equivalent:
457-
// std::floor(value * scale + 0.5) / scale
454+
return std::floor(value * scale + 0.5) / scale; // like in JS
455+
// return std::round(value * scale) / scale;
458456
}
459457

460458
inline void round_coords(mapbox::geojson::point &xyz,

src/main.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,10 @@ PYBIND11_MODULE(_pybind11_geobuf, m)
9292
"indent"_a = "");
9393

9494
py::class_<Encoder>(m, "Encoder", py::module_local()) //
95-
.def(py::init<uint32_t>(), //
95+
.def(py::init<uint32_t, bool>(), //
9696
py::kw_only(),
97-
"max_precision"_a = std::pow(10, MAPBOX_GEOBUF_DEFAULT_PRECISION))
97+
"max_precision"_a = std::pow(10, MAPBOX_GEOBUF_DEFAULT_PRECISION),
98+
"only_xy"_a = false)
9899
//
99100
.def(
100101
"encode",
@@ -149,6 +150,7 @@ PYBIND11_MODULE(_pybind11_geobuf, m)
149150
.def(py::init<>())
150151
//
151152
.def("precision", &Decoder::precision)
153+
.def("dim", &Decoder::__dim)
152154
.def(
153155
"decode",
154156
[](Decoder &self, const std::string &geobuf, bool indent,
@@ -188,6 +190,7 @@ PYBIND11_MODULE(_pybind11_geobuf, m)
188190
"geojson"_a, //
189191
"indent"_a = false, //
190192
"sort_keys"_a = false)
193+
.def("keys", &Decoder::__keys)
191194
//
192195
;
193196

0 commit comments

Comments
 (0)