Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 0 additions & 2 deletions jvm/src/test/resources/styles/multisprite/style.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
"name": "test-sprites",
"sources": {
"test_label": {
"minzoom": 0,
"maxzoom": 0,
"type": "geojson",
"data": {
"type": "FeatureCollection",
Expand Down
2 changes: 0 additions & 2 deletions jvm/src/test/resources/styles/style_geojson_ch_label.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@
"sources": {

"test_label": {
"minzoom": 0,
"maxzoom": 0,
"type": "geojson",
"data": {
"type": "FeatureCollection",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ Tiled2dMapVectorLayerParserResult Tiled2dMapVectorLayerParserHelper::parseStyleJ
minZoom = val.value("minzoom", 0);
maxZoom = val.value("maxzoom", 22);
}

std::optional<::RectCoord> bounds;
std::optional<std::string> coordinateReferenceSystem;

Expand Down Expand Up @@ -214,7 +214,7 @@ Tiled2dMapVectorLayerParserResult Tiled2dMapVectorLayerParserHelper::parseStyleJ
tileJsons[key] = val;
} else if (type == "geojson") {
nlohmann::json geojson;
Options options;
GeoJSONVT::Options options;

if (val["minzoom"].is_number_integer()) {
options.minZoom = val["minzoom"].get<uint8_t>();
Expand Down Expand Up @@ -302,7 +302,7 @@ Tiled2dMapVectorLayerParserResult Tiled2dMapVectorLayerParserHelper::parseStyleJ
metadata = json["metadata"].dump();
globalIsInteractable = parser.parseValue(json["metadata"]["interactable"]);
persistingSymbolPlacement = json["metadata"].value("persistingSymbolPlacement", false);

// XXX: make this a per sprite option?
if (json["metadata"].contains("use3xSprites")) {
use3xSprites = json["metadata"]["use3xSprites"].get<bool>();
Expand Down
4 changes: 2 additions & 2 deletions shared/src/map/layers/tiled/vector/geojson/GeoJsonVTFactory.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ class GeoJsonVTFactory {
public:
static std::shared_ptr<GeoJSONVTInterface> getGeoJsonVt(const std::shared_ptr<GeoJson> &geoJson,
const std::shared_ptr<StringInterner> &stringTable,
const Options& options = Options()) {
const GeoJSONVT::Options& options = GeoJSONVT::Options()) {
return std::static_pointer_cast<GeoJSONVTInterface>(std::make_shared<GeoJSONVT>(geoJson, stringTable, options));
}

static std::shared_ptr<GeoJSONVTInterface> getGeoJsonVt(const std::string &sourceName,
const std::string &geoJsonUrl,
const std::vector<std::shared_ptr<::LoaderInterface>> &loaders, const std::shared_ptr<Tiled2dMapVectorLayerLocalDataProviderInterface> &localDataProvider,
const std::shared_ptr<StringInterner> &stringTable,
const Options& options = Options()) {
const GeoJSONVT::Options& options = GeoJSONVT::Options()) {
std::shared_ptr<GeoJSONVT> vt = std::make_shared<GeoJSONVT>(sourceName, geoJsonUrl, loaders, localDataProvider, stringTable, options);
vt->load();
return vt;
Expand Down
113 changes: 73 additions & 40 deletions shared/src/map/layers/tiled/vector/geojson/geojsonvt/geojsonvt.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,48 +14,59 @@
#include <cmath>
#include <unordered_map>

struct TileOptions {
// simplification tolerance (higher means simpler)
double tolerance = 1.0;
class GeoJSONVT: public GeoJSONVTInterface, public std::enable_shared_from_this<GeoJSONVT> {
private:
struct TileOptions {
// simplification tolerance (higher means simpler)
double tolerance = 1.0;

// tile extent
uint32_t extent = 4096;
// tile extent
uint32_t extent = 4096;

// tile buffer on each side
uint32_t buffer = 64;
};
// tile buffer on each side
uint32_t buffer = 64;

struct Options : TileOptions {
// min zoom to will be visible
uint8_t minZoom = 0;
// min zoom to will be visible
uint8_t minZoom = 0;

// max zoom to preserve detail on
uint8_t maxZoom = 18;
// max zoom to preserve detail on
uint8_t maxZoom = 18;

// max zoom in the tile index
uint8_t indexMaxZoom = 5;
// max zoom in the tile index
uint8_t indexMaxZoom = 5;

// max number of points per tile in the tile index
uint32_t indexMaxPoints = 100000;
};
// max number of points per tile in the tile index
uint32_t indexMaxPoints = 100000;
};

inline uint64_t toID(uint8_t z, uint32_t x, uint32_t y) {
return (((1ull << z) * y + x) * 32) + z;
}
inline uint64_t toID(uint8_t z, uint32_t x, uint32_t y) {
return (((1ull << z) * y + x) * 32) + z;
}


public:
struct Options {
std::optional<uint8_t> minZoom;
std::optional<uint8_t> maxZoom;
std::optional<uint32_t> extent;
};

class GeoJSONVT: public GeoJSONVTInterface, public std::enable_shared_from_this<GeoJSONVT> {
private:
std::weak_ptr<StringInterner> stringTable;

public:
Options options;
// effective parameter values for tile generation
TileOptions options;

// parameters as configured in style
Options configuredOptions;

const Tile emptyTile = Tile();

public:
GeoJSONVT(const std::shared_ptr<GeoJson> &geoJson,
const std::shared_ptr<StringInterner> &stringTable,
const Options &options_ = Options())
: options(options_)
Options _config = Options())
: configuredOptions(_config)
, loadingResult(DataLoaderResult(std::nullopt, std::nullopt, LoaderStatus::OK, std::nullopt))
, stringTable(stringTable) {
initialize(geoJson);
Expand All @@ -65,8 +76,8 @@ class GeoJSONVT: public GeoJSONVTInterface, public std::enable_shared_from_this<
const std::vector<std::shared_ptr<::LoaderInterface>> &loaders,
const std::shared_ptr<Tiled2dMapVectorLayerLocalDataProviderInterface> &localDataProvider,
const std::shared_ptr<StringInterner> &stringTable,
const Options &options_ = Options())
: options(options_)
Options configurationOptions = Options())
: configuredOptions(configurationOptions)
, sourceName(sourceName)
, geoJsonUrl(geoJsonUrl)
, loaders(loaders)
Expand Down Expand Up @@ -144,14 +155,7 @@ class GeoJSONVT: public GeoJSONVTInterface, public std::enable_shared_from_this<
}

void initialize(const std::shared_ptr<GeoJson> &geoJson) {
// If the GeoJSON contains only points, there is no need to split it into smaller tiles,
// as there are no opportunities for simplification, merging, or meaningful point reduction.
// Keep point-only sources on a single zoom level only if minZoom is explicitly configured (> 0).
// For the default minZoom=0 case, collapsing to z0 creates very large matching tolerances and can
// hide unrelated points due to symbol ownership deduplication.
if (geoJson->hasOnlyPoints && options.minZoom > 0) {
options.maxZoom = options.minZoom;
}
options = getEffectiveOptions(configuredOptions, geoJson->hasOnlyPoints);

const uint32_t z2 = 1u << options.maxZoom;

Expand Down Expand Up @@ -186,10 +190,7 @@ class GeoJSONVT: public GeoJSONVTInterface, public std::enable_shared_from_this<
}

void reload(const std::shared_ptr<GeoJson> &geoJson) override {
// Keep behavior consistent with initialize().
if (geoJson->hasOnlyPoints && options.minZoom > 0) {
options.maxZoom = options.minZoom;
}
options = getEffectiveOptions(configuredOptions, geoJson->hasOnlyPoints);

const uint32_t z2 = 1u << options.maxZoom;

Expand Down Expand Up @@ -357,4 +358,36 @@ class GeoJSONVT: public GeoJSONVTInterface, public std::enable_shared_from_this<
}
waitingPromises.clear();
}

static TileOptions getEffectiveOptions(const Options &config, bool hasOnlyPoints) {
TileOptions options; // initialize with defaults.
if (config.extent.has_value()) {
options.extent = *config.extent;
}
// If the GeoJSON contains only points, there is no need to split it into smaller tiles,
// as there are no opportunities for simplification, merging, or meaningful point reduction.
// Keep point-only sources on a single zoom level, unless minZoom and
// maxZoom are explicitly configured.
if (hasOnlyPoints && !(config.minZoom.has_value() && config.maxZoom.has_value())) {
if(config.minZoom.has_value()) {
options.minZoom = *config.minZoom;
options.maxZoom = *config.minZoom;
} else if (config.maxZoom.has_value()) {
options.minZoom = *config.maxZoom;
options.maxZoom = *config.maxZoom;
Comment on lines +375 to +377
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Do not collapse maxzoom-only point sources to minzoom=maxzoom

For point-only GeoJSON sources that specify only maxzoom, getEffectiveOptions() now forces both minZoom and maxZoom to that value. Tiled2dMapVectorGeoJSONLayerConfig::getZoomLevelInfos() therefore advertises only that one zoom level, and the default Tiled2dMapSource::onVisibleBoundsChanged() path drops the source at lower camera zooms because underzoom is disabled when the first available zoom identifier is not 0. A style like {"type":"geojson","maxzoom":25,...} used to render across z0..25; after this change it will stay invisible until the camera reaches z25.

Useful? React with 👍 / 👎.

} else {
options.minZoom = 0;
options.maxZoom = 0;
}
} else {
if(config.minZoom.has_value()) {
options.minZoom = *config.minZoom;
}
if(config.maxZoom.has_value()) {
options.maxZoom = *config.maxZoom;
}
}
return options;
}

};
40 changes: 32 additions & 8 deletions shared/test/TestStyleParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,48 @@ class TestGeoJSONTileDelegate : public GeoJSONTileDelegate, public ActorObject {
void failedToLoad() override { failedToLoadCalled = true; }
};

TEST_CASE("TestStyleParser", "[GeoJson inline]") {
auto jsonString = TestData::readFileToString("style/geojson_style_inline.json");
static void testGeojsonInlineParse(const char* filename, int expectedMinZoom, int expectedMaxZoom) {
auto jsonString = TestData::readFileToString(filename);
std::shared_ptr<StringInterner> stringTable = std::make_shared<StringInterner>(ValueKeys::newStringInterner());
auto result = Tiled2dMapVectorLayerParserHelper::parseStyleJsonFromString("test", jsonString, nullptr, {}, stringTable, {});
REQUIRE(result.mapDescription != nullptr);
REQUIRE(!result.mapDescription->geoJsonSources.empty());

std::shared_ptr<GeoJSONVTInterface> geojsonSource = result.mapDescription->geoJsonSources.begin()->second;
REQUIRE(geojsonSource->getMinZoom() == 0);
REQUIRE(geojsonSource->getMaxZoom() == 0);
REQUIRE(geojsonSource->getMinZoom() == expectedMinZoom);
REQUIRE(geojsonSource->getMaxZoom() == expectedMaxZoom);
REQUIRE(result.mapDescription->layers[0]->sourceMinZoom == expectedMinZoom);
REQUIRE(result.mapDescription->layers[0]->sourceMaxZoom == expectedMaxZoom);

REQUIRE(result.mapDescription->layers[0]->sourceMinZoom == 0);
REQUIRE(result.mapDescription->layers[0]->sourceMaxZoom == 0);
REQUIRE_NOTHROW(geojsonSource->getTile(0, 0, 0));
REQUIRE_THROWS(geojsonSource->getTile(6, 33, 22));
if(expectedMaxZoom >= 6) {
REQUIRE_NOTHROW(geojsonSource->getTile(6, 33, 22));
} else {
REQUIRE_THROWS(geojsonSource->getTile(6, 33, 22));
}
}

TEST_CASE("GeoJson inline points min and maxzoom", "[TestStyleParser]") {
testGeojsonInlineParse("style/geojson_style_points_minmaxzoom.json", 0, 25);
}

TEST_CASE("TestStyleParser", "[GeoJson local provider]") {
TEST_CASE("GeoJson inline points no min/maxzoom", "[TestStyleParser]") {
testGeojsonInlineParse("style/geojson_style_points_nominmaxzoom.json", 0, 0);
};

TEST_CASE("GeoJson inline points minzoom", "[TestStyleParser]") {
testGeojsonInlineParse("style/geojson_style_points_minzoom.json", 5, 5);
};

TEST_CASE("GeoJson inline points maxzoom", "[TestStyleParser]") {
testGeojsonInlineParse("style/geojson_style_points_maxzoom.json", 25, 25);
};

TEST_CASE("GeoJson inline mixed no min/maxzoom", "[TestStyleParser]") {
testGeojsonInlineParse("style/geojson_style_mixed_nominmaxzoom.json", 0, 18);
};

TEST_CASE("GeoJson local provider", "[TestStyleParser]") {
auto jsonString = TestData::readFileToString("style/geojson_style_provider.json");
auto provider =
std::make_shared<TestLocalDataProvider>(std::unordered_map<std::string, std::string>{{"wsource", "geojson.geojson"}});
Expand Down
51 changes: 51 additions & 0 deletions shared/test/data/style/geojson_style_mixed_nominmaxzoom.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
"version": 8,
"name": "s",
"sources": {
"wsource": {
"type": "geojson",
"data": {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates":
[8.5, 46.8]

}
},
{
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": [
[102.0, 0.0],
[103.0, 1.0],
[104.0, 0.0],
[105.0, 1.0]
]
}
}
]
}
}
},
"sprite": "localdata-sprites",
"layers": [
{
"id": "w",
"type": "fill",
"metadata": {
"blend-mode": "multiply"
},
"minzoom": 0,
"maxzoom": 6,
"source": "wsource",
"paint": {
"fill-color": "rgb(202, 154, 255)"
}
}
]
}
30 changes: 30 additions & 0 deletions shared/test/data/style/geojson_style_points_maxzoom.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"version": 8,
"name": "s",
"sources": {
"wsource": {
"type": "geojson",
"maxzoom": 25,
"data": {
"type": "Point",
"coordinates": [102.0, 0.5]
}
}
},
"sprite": "localdata-sprites",
"layers": [
{
"id": "w",
"type": "fill",
"metadata": {
"blend-mode": "multiply"
},
"minzoom": 0,
"maxzoom": 6,
"source": "wsource",
"paint": {
"fill-color": "rgb(202, 154, 255)"
}
}
]
}
30 changes: 30 additions & 0 deletions shared/test/data/style/geojson_style_points_minzoom.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"version": 8,
"name": "s",
"sources": {
"wsource": {
"type": "geojson",
"minzoom": 5,
"data": {
"type": "Point",
"coordinates": [102.0, 0.5]
}
}
},
"sprite": "localdata-sprites",
"layers": [
{
"id": "w",
"type": "fill",
"metadata": {
"blend-mode": "multiply"
},
"minzoom": 0,
"maxzoom": 6,
"source": "wsource",
"paint": {
"fill-color": "rgb(202, 154, 255)"
}
}
]
}
Loading
Loading