mirror of
https://github.com/mapbox/tippecanoe.git
synced 2025-04-12 21:33:00 +00:00
Add an option for the Visvalingam simplification algorithm (#16)
* First attempt at porting Paul Mach's Visvalingam implementation * Make indent * Mostly working * Approximate equivalence from Douglas-Peucker to Visvalingam levels * Don't simplify away tile boundary crossing points * Add a command-line option and test for Visvalingam simplification * Update changelog * Cleanup in response to review feedback * Include <stdio.h> to fix compiler warnings
This commit is contained in:
parent
6de00c15ec
commit
4ef430b913
CHANGELOG.mdMakefileREADME.mdgeometry.cppgeometry.hppmain.cpp
man
options.hpptests/ne_110m_admin_0_countries/out
version.hppvisvalingam.cpp@ -1,3 +1,7 @@
|
||||
## 2.7.0
|
||||
|
||||
* Add the option to use the Visvalingam simplification algorithm
|
||||
|
||||
## 2.6.4
|
||||
|
||||
* Update tests that should have been updated in 2.6.2
|
||||
|
2
Makefile
2
Makefile
@ -47,7 +47,7 @@ C = $(wildcard *.c) $(wildcard *.cpp)
|
||||
INCLUDES = -I/usr/local/include -I.
|
||||
LIBS = -L/usr/local/lib
|
||||
|
||||
tippecanoe: geojson.o jsonpull/jsonpull.o tile.o pool.o mbtiles.o geometry.o projection.o memfile.o mvt.o serial.o main.o text.o dirtiles.o plugin.o read_json.o write_json.o geobuf.o flatgeobuf.o evaluator.o geocsv.o csv.o geojson-loop.o json_logger.o
|
||||
tippecanoe: geojson.o jsonpull/jsonpull.o tile.o pool.o mbtiles.o geometry.o projection.o memfile.o mvt.o serial.o main.o text.o dirtiles.o plugin.o read_json.o write_json.o geobuf.o flatgeobuf.o evaluator.o geocsv.o csv.o geojson-loop.o json_logger.o visvalingam.o
|
||||
$(CXX) $(PG) $(LIBS) $(FINAL_FLAGS) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -lm -lz -lsqlite3 -lpthread
|
||||
|
||||
tippecanoe-enumerate: enumerate.o
|
||||
|
@ -483,6 +483,7 @@ the same layer, enclose them in an `all` expression so they will all be evaluate
|
||||
* `-pn` or `--no-simplification-of-shared-nodes`: Don't simplify away nodes that appear in more than one feature or are used multiple times within the same feature, so that the intersection node will not be lost from intersecting roads. (This will not be effective if you also use `--coalesce` or `--detect-shared-borders`.)
|
||||
* `-pt` or `--no-tiny-polygon-reduction`: Don't combine the area of very small polygons into small squares that represent their combined area.
|
||||
* `--tiny-polygon-size=`_size_: Use the specified _size_ for tiny polygons instead of the default 2. Anything above 6 or so will lead to visible artifacts with the default tile detail.
|
||||
* `-av` or `--visvalingam`: Use Visvalingam's simplification algorithm rather than Douglas-Peucker's.
|
||||
|
||||
### Attempts to improve shared polygon boundaries
|
||||
|
||||
|
13
geometry.cpp
13
geometry.cpp
@ -849,8 +849,19 @@ drawvec simplify_lines(drawvec &geom, int z, int detail, bool mark_tile_bounds,
|
||||
geom[i].necessary = 1;
|
||||
geom[j - 1].necessary = 1;
|
||||
|
||||
// empirical mapping from douglas-peucker simplifications
|
||||
// to visvalingam simplifications that yield similar
|
||||
// output sizes
|
||||
double sim = simplification * (0.1596 * z + 0.878);
|
||||
double scale = (res * sim) * (res * sim);
|
||||
scale = exp(1.002 * log(scale) + 0.3043);
|
||||
|
||||
if (j - i > 1) {
|
||||
douglas_peucker(geom, i, j - i, res * simplification, 2, retain);
|
||||
if (additional[A_VISVALINGAM]) {
|
||||
visvalingam(geom, i, j, scale, retain);
|
||||
} else {
|
||||
douglas_peucker(geom, i, j - i, res * simplification, 2, retain);
|
||||
}
|
||||
}
|
||||
i = j - 1;
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include <vector>
|
||||
#include <atomic>
|
||||
#include <sqlite3.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define VT_POINT 1
|
||||
#define VT_LINE 2
|
||||
@ -79,5 +80,6 @@ double get_mp_area(drawvec &geom);
|
||||
drawvec simple_clip_poly(drawvec &geom, long long x1, long long y1, long long x2, long long y2);
|
||||
drawvec clip_lines(drawvec &geom, long long x1, long long y1, long long x2, long long y2);
|
||||
drawvec clip_point(drawvec &geom, long long x1, long long y1, long long x2, long long y2);
|
||||
void visvalingam(drawvec &ls, size_t start, size_t end, double threshold, size_t retain);
|
||||
|
||||
#endif
|
||||
|
1
main.cpp
1
main.cpp
@ -2730,6 +2730,7 @@ int main(int argc, char **argv) {
|
||||
{"no-tiny-polygon-reduction", no_argument, &prevent[P_TINY_POLYGON_REDUCTION], 1},
|
||||
{"tiny-polygon-size", required_argument, 0, '~'},
|
||||
{"no-simplification-of-shared-nodes", no_argument, &prevent[P_SIMPLIFY_SHARED_NODES], 1},
|
||||
{"visvalingam", no_argument, &additional[A_VISVALINGAM], 1},
|
||||
|
||||
{"Attempts to improve shared polygon boundaries", 0, 0, 0},
|
||||
{"detect-shared-borders", no_argument, &additional[A_DETECT_SHARED_BORDERS], 1},
|
||||
|
@ -35,7 +35,7 @@ For this fork you will need to build from the source repository:
|
||||
.PP
|
||||
.RS
|
||||
.nf
|
||||
$ git clone https://github.com/protomaps/tippecanoe.git
|
||||
$ git clone https://github.com/felt/tippecanoe.git
|
||||
$ cd tippecanoe
|
||||
$ make \-j
|
||||
$ make install
|
||||
@ -616,6 +616,8 @@ the line or polygon within one tile unit of its proper location. You can probabl
|
||||
\fB\fC\-pt\fR or \fB\fC\-\-no\-tiny\-polygon\-reduction\fR: Don't combine the area of very small polygons into small squares that represent their combined area.
|
||||
.IP \(bu 2
|
||||
\fB\fC\-\-tiny\-polygon\-size=\fR\fIsize\fP: Use the specified \fIsize\fP for tiny polygons instead of the default 2. Anything above 6 or so will lead to visible artifacts with the default tile detail.
|
||||
.IP \(bu 2
|
||||
\fB\fC\-av\fR or \fB\fC\-\-visvalingam\fR: Use Visvalingam's simplification algorithm rather than Douglas\-Peucker's.
|
||||
.RE
|
||||
.SS Attempts to improve shared polygon boundaries
|
||||
.RS
|
||||
|
@ -25,6 +25,7 @@
|
||||
#define A_GENERATE_IDS ((int) 'i')
|
||||
#define A_CONVERT_NUMERIC_IDS ((int) 'I')
|
||||
#define A_HILBERT ((int) 'h')
|
||||
#define A_VISVALINGAM ((int) 'v')
|
||||
|
||||
#define P_SIMPLIFY ((int) 's')
|
||||
#define P_SIMPLIFY_LOW ((int) 'S')
|
||||
|
File diff suppressed because one or more lines are too long
@ -1,6 +1,6 @@
|
||||
#ifndef VERSION_HPP
|
||||
#define VERSION_HPP
|
||||
|
||||
#define VERSION "v2.6.4"
|
||||
#define VERSION "v2.7.0"
|
||||
|
||||
#endif
|
||||
|
230
visvalingam.cpp
Normal file
230
visvalingam.cpp
Normal file
@ -0,0 +1,230 @@
|
||||
// Adapted from
|
||||
// https://github.com/paulmach/orb/blob/dcade4901baea0727377ccf7c4aab2addd92d152/simplify/visvalingam.go
|
||||
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) 2017 Paul Mach
|
||||
//
|
||||
// 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.
|
||||
|
||||
#include <vector>
|
||||
#include <cmath>
|
||||
#include "geometry.hpp"
|
||||
|
||||
// Stuff to create the priority queue, or min heap.
|
||||
// Rewriting it here, vs using the std lib, resulted in a 50% performance bump!
|
||||
|
||||
struct visItem {
|
||||
double area = 0; // triangle area
|
||||
int pointIndex = 0; // index of point in original path
|
||||
|
||||
// to keep a virtual linked list to help rebuild the triangle areas as we remove points.
|
||||
visItem *next = NULL;
|
||||
visItem *previous = NULL;
|
||||
|
||||
int index = 0; // interal index in heap, for removal and update
|
||||
};
|
||||
|
||||
struct minHeap {
|
||||
std::vector<visItem *> h;
|
||||
|
||||
void Push(visItem *item) {
|
||||
item->index = h.size();
|
||||
h.push_back(item);
|
||||
up(item->index);
|
||||
}
|
||||
|
||||
visItem *Pop() {
|
||||
visItem *removed = h[0];
|
||||
visItem *lastItem = h[h.size() - 1];
|
||||
h.pop_back();
|
||||
|
||||
if (h.size() > 0) {
|
||||
lastItem->index = 0;
|
||||
h[0] = lastItem;
|
||||
down(0);
|
||||
}
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
void Update(visItem *item, double area) {
|
||||
if (item->area > area) {
|
||||
// area got smaller
|
||||
item->area = area;
|
||||
up(item->index);
|
||||
} else {
|
||||
// area got larger
|
||||
item->area = area;
|
||||
down(item->index);
|
||||
}
|
||||
}
|
||||
|
||||
void up(int i) {
|
||||
visItem *object = h[i];
|
||||
while (i > 0) {
|
||||
int up = ((i + 1) >> 1) - 1;
|
||||
visItem *parent = h[up];
|
||||
|
||||
if (parent->area <= object->area) {
|
||||
// parent is smaller so we're done fixing up the heap.
|
||||
break;
|
||||
}
|
||||
|
||||
// swap nodes
|
||||
parent->index = i;
|
||||
h[i] = parent;
|
||||
|
||||
object->index = up;
|
||||
h[up] = object;
|
||||
|
||||
i = up;
|
||||
}
|
||||
}
|
||||
|
||||
void down(int i) {
|
||||
visItem *object = h[i];
|
||||
while (1) {
|
||||
size_t right = (i + 1) << 1;
|
||||
size_t left = right - 1;
|
||||
|
||||
int down = i;
|
||||
visItem *child = h[down];
|
||||
|
||||
// swap with smallest child
|
||||
if (left < h.size() && h[left]->area < child->area) {
|
||||
down = left;
|
||||
child = h[down];
|
||||
}
|
||||
|
||||
if (right < h.size() && h[right]->area < child->area) {
|
||||
down = right;
|
||||
child = h[down];
|
||||
}
|
||||
|
||||
// non smaller, so quit
|
||||
if (down == i) {
|
||||
break;
|
||||
}
|
||||
|
||||
// swap the nodes
|
||||
child->index = i;
|
||||
h[child->index] = child;
|
||||
|
||||
object->index = down;
|
||||
h[down] = object;
|
||||
|
||||
i = down;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static double doubleTriangleArea(drawvec const &ls, int start, int i1, int i2, int i3) {
|
||||
draw a = ls[i1 + start];
|
||||
draw b = ls[i2 + start];
|
||||
draw c = ls[i3 + start];
|
||||
|
||||
return std::abs((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x));
|
||||
}
|
||||
|
||||
void visvalingam(drawvec &ls, size_t start, size_t end, double threshold, size_t retain) {
|
||||
// edge cases checked, get on with it
|
||||
int removed = 0;
|
||||
threshold *= 2;
|
||||
|
||||
// build the initial minheap linked list.
|
||||
minHeap heap;
|
||||
|
||||
visItem linkedListStart;
|
||||
linkedListStart.area = INFINITY;
|
||||
linkedListStart.pointIndex = 0;
|
||||
heap.Push(&linkedListStart);
|
||||
|
||||
// internal path items
|
||||
std::vector<visItem> items;
|
||||
items.resize((end - start));
|
||||
|
||||
{
|
||||
visItem *previous = &linkedListStart;
|
||||
for (size_t i = 1; i < (end - start) - 1; i++) {
|
||||
visItem *item = &items[i];
|
||||
|
||||
item->area = doubleTriangleArea(ls, start, i - 1, i, i + 1);
|
||||
item->pointIndex = i;
|
||||
item->previous = previous;
|
||||
|
||||
heap.Push(item);
|
||||
previous->next = item;
|
||||
previous = item;
|
||||
}
|
||||
|
||||
// final item
|
||||
visItem *endItem = &items[(end - start) - 1];
|
||||
endItem->area = INFINITY;
|
||||
endItem->pointIndex = (end - start) - 1;
|
||||
endItem->previous = previous;
|
||||
|
||||
previous->next = endItem;
|
||||
heap.Push(endItem);
|
||||
}
|
||||
|
||||
// run through the reduction process
|
||||
while (heap.h.size() > 0) {
|
||||
visItem *current = heap.Pop();
|
||||
if (current->area > threshold || (end - start) - removed <= retain) {
|
||||
break;
|
||||
}
|
||||
|
||||
visItem *next = current->next;
|
||||
visItem *previous = current->previous;
|
||||
|
||||
// remove current element from linked list
|
||||
previous->next = current->next;
|
||||
next->previous = current->previous;
|
||||
removed++;
|
||||
|
||||
// figure out the new areas
|
||||
if (previous->previous != NULL) {
|
||||
double area = doubleTriangleArea(ls, start,
|
||||
previous->previous->pointIndex,
|
||||
previous->pointIndex,
|
||||
next->pointIndex);
|
||||
|
||||
area = std::max(area, current->area);
|
||||
heap.Update(previous, area);
|
||||
}
|
||||
|
||||
if (next->next != NULL) {
|
||||
double area = doubleTriangleArea(ls, start,
|
||||
previous->pointIndex,
|
||||
next->pointIndex,
|
||||
next->next->pointIndex);
|
||||
|
||||
area = std::max(area, current->area);
|
||||
heap.Update(next, area);
|
||||
}
|
||||
}
|
||||
|
||||
visItem *item = &linkedListStart;
|
||||
while (item != NULL) {
|
||||
ls[item->pointIndex + start].necessary = 1;
|
||||
item = item->next;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user