feat(parser): support MultiGeometry placemarks - #16
Merged
Conversation
GeometryType already declared MULTI_GEOMETRY, but the validator fed the element to validateGeometryCoordinates(), which looks for a coordinates child a MultiGeometry does not have. Every document containing one was rejected at load time with "Empty coordinates in geometry", and the parser had no handling for it either. MultiGeometry is what Google My Maps and ogr2ogr emit for a feature made of several shapes, so this covers a large slice of real world files. The validator now walks the nested geometries and validates each one, recursing when a MultiGeometry contains another, and rejects one that is empty. The parser returns the placemark as type MultiGeometry with a geometries list instead of coordinates, and toGeoJson() maps it onto a GeoJSON GeometryCollection, which nests the same way. Geometry parsing and GeoJSON emission were both open coded if/elseif chains; they are now dispatched off GeometryType so a new geometry is added in one place. Output for Point, LineString and Polygon is unchanged, byte for byte.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
GeometryTypehas declaredMULTI_GEOMETRY = 'MultiGeometry'since the enum was introduced, andKmlValidatorloops overGeometryType::cases(). So when a Placemark holds a<MultiGeometry>, the validator matches it and hands the element tovalidateGeometryCoordinates(), which does:A
MultiGeometryhas nocoordinateschild. It holds nested geometries. So every document containing a MultiGeometry is rejected at load time:And even past validation,
getPlacemarks()had no branch for it: it checks->Point,->LineString,->Polygonand nothing else, so the placemark would have come back with a name and no geometry at all.This is not an exotic corner of the spec. MultiGeometry is what Google My Maps and
ogr2ogremit for any feature made of more than one shape, which is a large share of the KML actually in circulation.Fix
Validator walks the nested geometries and validates each one, recursing when a MultiGeometry contains another (KML permits that), and rejects one that is empty:
Coordinate validation still applies to everything inside, so an out of range latitude nested three levels deep is still caught.
Parser returns the placemark as:
[ 'name' => 'Mixed', 'description' => '...', 'type' => 'MultiGeometry', 'geometries' => [ ['type' => 'Point', 'coordinates' => [...]], ['type' => 'LineString', 'coordinates' => [...]], ['type' => 'Polygon', 'coordinates' => ['outerBoundary' => [...], 'innerBoundaries' => [...]]], ], ]geometriesinstead ofcoordinates, since there is no single coordinate set. Each entry has the same shape a standalone placemark of that type would have.toGeoJson()maps it onto a GeoJSONGeometryCollection, which nests the same way a MultiGeometry does.Refactor
Geometry parsing and GeoJSON emission were both open-coded
if/elseifchains repeating the same coordinate flattening three times. Both now dispatch offGeometryType, so adding a geometry means onematcharm in each rather than hunting through two methods. This is what makes the recursion possible without duplicating the whole chain.Output parity
The refactor touches the code path of every existing geometry type, so parity was checked rather than assumed:
toGeoJson()+getPlacemarks()+getStyles()output fortests/files/kml-example/base.kml, dumped frommainand from this branch: byte identical (6628 bytes both). That sample only contains Points, so on its own it proves little.tests/GeoJsonOutputTest.phpasserts the exact feature arrays for a LineString and for a Polygon with an inner ring, includingstyleUrlandextendedDatain the properties and the outer-ring-first ordering. Those three tests were run againstmain'ssrc/as well and pass unchanged there.So Point, LineString and Polygon output is the same before and after.
Tests
tests/MultiGeometryTest.php, all failing onmain(the first four fail at load):coordinateskey on the placemarkGeometryCollectionwith the exact expected coordinatestests/GeoJsonOutputTest.phpadds the LineString and Polygon coverage described above, which the suite did not have at all before: no test touched either geometry type.Behaviour change
If a Placemark somehow contained two sibling geometries (
<Point>and<Polygon>), the oldgetPlacemarks()ran everyifand the last one won, while the validator validated the first. The two disagreed. Both now take the first match, inGeometryTypedeclaration order. Invalid KML either way, but worth noting it resolves differently.Not in scope
LineStyle/PolyStyle,<Folder>nesting and<SimpleData>are all still unsupported. Separate PRs.