From bb525fcb1497bde0229c4299575a8aad16d59f33 Mon Sep 17 00:00:00 2001 From: Glenn Waldron Date: Tue, 7 Jan 2025 12:32:00 -0500 Subject: [PATCH] Separate FeatureStyleSorter into its own file --- src/osgEarth/CMakeLists.txt | 2 + src/osgEarth/FeatureImageLayer.cpp | 1 + src/osgEarth/FeatureRasterizer | 63 +--- src/osgEarth/FeatureRasterizer.cpp | 313 ------------------- src/osgEarth/FeatureSDFLayer.cpp | 1 + src/osgEarth/FeatureStyleSorter | 86 +++++ src/osgEarth/FeatureStyleSorter.cpp | 328 ++++++++++++++++++++ src/osgEarthProcedural/RoadSurfaceLayer.cpp | 2 +- 8 files changed, 420 insertions(+), 376 deletions(-) create mode 100644 src/osgEarth/FeatureStyleSorter create mode 100644 src/osgEarth/FeatureStyleSorter.cpp diff --git a/src/osgEarth/CMakeLists.txt b/src/osgEarth/CMakeLists.txt index 346bff4f0f..2b30c02c11 100644 --- a/src/osgEarth/CMakeLists.txt +++ b/src/osgEarth/CMakeLists.txt @@ -201,6 +201,7 @@ SET(TARGET_H FeatureSDFLayer FeatureSource FeatureSourceIndexNode + FeatureStyleSorter FileUtils Fill Filter @@ -564,6 +565,7 @@ set(TARGET_SRC FeatureSDFLayer.cpp FeatureSource.cpp FeatureSourceIndexNode.cpp + FeatureStyleSorter.cpp FileGDBFeatureSource.cpp FileUtils.cpp Fill.cpp diff --git a/src/osgEarth/FeatureImageLayer.cpp b/src/osgEarth/FeatureImageLayer.cpp index e18e146a9d..77866efeaf 100644 --- a/src/osgEarth/FeatureImageLayer.cpp +++ b/src/osgEarth/FeatureImageLayer.cpp @@ -23,6 +23,7 @@ #include #include #include +#include using namespace osgEarth; diff --git a/src/osgEarth/FeatureRasterizer b/src/osgEarth/FeatureRasterizer index 461246aba6..7eff15a401 100644 --- a/src/osgEarth/FeatureRasterizer +++ b/src/osgEarth/FeatureRasterizer @@ -16,8 +16,7 @@ * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see */ -#ifndef OSGEARTH_FEATURE_RASTERIZER -#define OSGEARTH_FEATURE_RASTERIZER 1 +#pragma once #include #include @@ -102,65 +101,5 @@ namespace osgEarth const FeatureProfile* profile, const StyleSheet* sheet); }; - - class OSGEARTH_EXPORT FeatureStyleSorter - { - public: - FeatureStyleSorter(); - - using Function = std::function; - - void sort( - const TileKey& key, - const Distance& buffer, - Session* session, - const FeatureFilterChain& filters, - Function processFeaturesForStyle, - ProgressCallback* progress) const; - - protected: - void getFeatures( - Session* session, - const Query& query, - const Distance& buffer, - const GeoExtent& extent, - const FeatureFilterChain& filters, - FeatureList& output, - ProgressCallback* progress) const; - - void sort_usingEmbeddedStyles( - const TileKey& key, - const Distance& buffer, - const FeatureFilterChain& filters, - Session* session, - Function processFeaturesForStyle, - ProgressCallback* progress) const; - - void sort_usingSelectors( - const TileKey& key, - const Distance& buffer, - const FeatureFilterChain& filters, - Session* session, - Function processFeaturesForStyle, - ProgressCallback* progress) const; - - void sort_usingOneStyle( - const Style& style, - const TileKey& key, - const Distance& buffer, - const FeatureFilterChain& filters, - Session* session, - Function processFeaturesForStyle, - ProgressCallback* progress) const; - - - }; } } - - -#endif diff --git a/src/osgEarth/FeatureRasterizer.cpp b/src/osgEarth/FeatureRasterizer.cpp index 1d82c2698a..9fb5427a00 100644 --- a/src/osgEarth/FeatureRasterizer.cpp +++ b/src/osgEarth/FeatureRasterizer.cpp @@ -1361,316 +1361,3 @@ FeatureRasterizer::finalize() return GeoImage(_image.release(), _extent); } - -//........................................................................ - -FeatureStyleSorter::FeatureStyleSorter() -{ - //nop -} - -void -FeatureStyleSorter::sort_usingEmbeddedStyles( - const TileKey& key, - const Distance& buffer, - const FeatureFilterChain& filters, - Session* session, - FeatureStyleSorter::Function processFeaturesForStyle, - ProgressCallback* progress) const -{ - // Each feature has its own embedded style data, so use that: - FilterContext context; - - Query query; - query.tileKey() = key; - query.buffer() = buffer; - - osg::ref_ptr cursor = session->getFeatureSource()->createFeatureCursor(query, filters, &context, progress); - - while (cursor.valid() && cursor->hasMore()) - { - auto feature = cursor->nextFeature(); - if (feature) - { - FeatureList data; - data.push_back(feature); - processFeaturesForStyle(feature->style().get(), data, progress); - } - } -} - -void -FeatureStyleSorter::sort_usingSelectors( - const TileKey& key, - const Distance& buffer, - const FeatureFilterChain& filters, - Session* session, - FeatureStyleSorter::Function processFeaturesForStyle, - ProgressCallback* progress) const -{ - FeatureSource* features = session->getFeatureSource(); - - Query defaultQuery; - defaultQuery.tileKey() = key; - - for (auto& iter : session->styles()->getSelectors()) - { - const StyleSelector& sel = iter.second; - if (sel.styleExpression().isSet()) - { - const FeatureProfile* featureProfile = features->getFeatureProfile(); - - // establish the working bounds and a context: - FilterContext context(session, featureProfile); - StringExpression styleExprCopy(sel.styleExpression().get()); - - FeatureList features; - getFeatures(session, defaultQuery, buffer, key.getExtent(), filters, features, progress); - if (!features.empty()) - { - std::unordered_map> literal_styles; - - // keep ordered. - std::map> style_buckets; - - for (FeatureList::iterator itr = features.begin(); itr != features.end(); ++itr) - { - Feature* feature = itr->get(); - - const std::string& styleString = feature->eval(styleExprCopy, &context); - if (!styleString.empty() && styleString != "null") - { - // resolve the style: - const Style* resolved_style = nullptr; - int resolved_index = 0; - - // if the style string begins with an open bracket, it's an inline style definition. - if (styleString.length() > 0 && styleString[0] == '{') - { - Config conf("style", styleString); - conf.setReferrer(sel.styleExpression().get().uriContext().referrer()); - conf.set("type", "text/css"); - auto& literal_style_and_index = literal_styles[conf.toJSON()]; - if (literal_style_and_index.first.empty()) - { - literal_style_and_index.first = Style(conf); - // literal styles always come AFTER sheet styles - literal_style_and_index.second = literal_styles.size() + session->styles()->getStyles().size(); - } - resolved_style = &literal_style_and_index.first; - resolved_index = literal_style_and_index.second; - } - - // otherwise, look up the style in the stylesheet. Do NOT fall back on a default - // style in this case: for style expressions, the user must be explicit about - // default styling; this is because there is no other way to exclude unwanted - // features. - else - { - auto style_and_index = session->styles()->getStyleAndIndex(styleString); - - //const Style* selected_style = session->styles()->getStyle(styleString, false); - if (style_and_index.first) - { - resolved_style = style_and_index.first; - resolved_index = style_and_index.second; - } - } - - if (resolved_style) - { - auto& bucket = style_buckets[resolved_index]; - bucket.first = resolved_style; - bucket.second.emplace_back(feature); - } - } - } - - // in order: - for (auto& iter : style_buckets) - { - const Style* style = iter.second.first; - FeatureList& list = iter.second.second; - processFeaturesForStyle(*style, list, progress); - } - } - } - else - { - const Style* style = session->styles()->getStyle(sel.getSelectedStyleName()); - Query query = sel.query().get(); - query.tileKey() = key; - - // Get the features - FeatureList features; - getFeatures(session, query, buffer, key.getExtent(), filters, features, progress); - - processFeaturesForStyle(*style, features, progress); - } - } -} - -void -FeatureStyleSorter::sort_usingOneStyle( - const Style& style, - const TileKey& key, - const Distance& buffer, - const FeatureFilterChain& filters, - Session* session, - Function processFeaturesForStyle, - ProgressCallback* progress) const -{ - Query defaultQuery; - defaultQuery.tileKey() = key; - - FeatureList features; - getFeatures(session, defaultQuery, buffer, key.getExtent(), filters, features, progress); - - processFeaturesForStyle(style, features, progress); -} - -void -FeatureStyleSorter::sort( - const TileKey& key, - const Distance& buffer, - Session* session, - const FeatureFilterChain& filters, - Function processFeaturesForStyle, - ProgressCallback* progress) const -{ - OE_SOFT_ASSERT_AND_RETURN(session, void()); - OE_SOFT_ASSERT_AND_RETURN(session->getFeatureSource(), void()); - OE_SOFT_ASSERT_AND_RETURN(session->getFeatureSource()->getFeatureProfile(), void()); - - OE_PROFILING_ZONE; - - if (session->getFeatureSource()->hasEmbeddedStyles()) - { - sort_usingEmbeddedStyles( - key, - buffer, - filters, - session, - processFeaturesForStyle, - progress); - - } - else if (session->styles()) - { - if (session->styles()->getSelectors().size() > 0) - { - sort_usingSelectors( - key, - buffer, - filters, - session, - processFeaturesForStyle, - progress); - - } - else - { - sort_usingOneStyle( - *session->styles()->getDefaultStyle(), - key, - buffer, - filters, - session, - processFeaturesForStyle, - progress); - } - } - else - { - sort_usingOneStyle( - Style(), // empty style - key, - buffer, - filters, - session, - processFeaturesForStyle, - progress); - } -} - -void -FeatureStyleSorter::getFeatures( - Session* session, - const Query& query, - const Distance& buffer, - const GeoExtent& workingExtent, - const FeatureFilterChain& filters, - FeatureList& features, - ProgressCallback* progress) const -{ - OE_SOFT_ASSERT_AND_RETURN(session != nullptr, void()); - OE_SOFT_ASSERT_AND_RETURN(session->getFeatureSource() != nullptr, void()); - OE_SOFT_ASSERT_AND_RETURN(session->getFeatureSource()->getFeatureProfile() != nullptr, void()); - OE_SOFT_ASSERT_AND_RETURN(workingExtent.isValid(), void()); - - OE_PROFILING_ZONE; - - // first we need the overall extent of the layer: - const GeoExtent& featuresExtent = session->getFeatureSource()->getFeatureProfile()->getExtent(); - - // convert them both to WGS84, intersect the extents, and convert back. - GeoExtent featuresExtentWGS84 = featuresExtent.transform(featuresExtent.getSRS()->getGeographicSRS()); - GeoExtent workingExtentWGS84 = workingExtent.transform(featuresExtent.getSRS()->getGeographicSRS()); - GeoExtent queryExtentWGS84 = featuresExtentWGS84.intersectionSameSRS(workingExtentWGS84); - if (queryExtentWGS84.isValid()) - { - GeoExtent queryExtent = queryExtentWGS84.transform(featuresExtent.getSRS()); - - // incorporate the image extent into the feature query for this style: - Query localQuery = query; - localQuery.bounds() = - query.bounds().isSet() ? unionOf(query.bounds().get(), queryExtent.bounds()) : - queryExtent.bounds(); - - FilterContext context(session, session->getFeatureSource()->getFeatureProfile(), queryExtent); - - // now copy the resulting feature set into a list, converting the data - // types along the way if a geometry override is in place: - while (features.empty()) - { - if (progress && progress->isCanceled()) - break; - - osg::ref_ptr cursor; - - if (localQuery.tileKey().isSet()) - { - localQuery.buffer() = buffer; - } - - cursor = session->getFeatureSource()->createFeatureCursor(localQuery, filters, &context, progress); - - while (cursor.valid() && cursor->hasMore()) - { - Feature* feature = cursor->nextFeature(); - if (feature->getGeometry()) - { - features.push_back(feature); - } - } - - // If we didn't get any features and we have a tilekey set, try falling back. - if (features.empty() && - localQuery.tileKey().isSet() && - localQuery.tileKey()->valid()) - { - localQuery.tileKey() = localQuery.tileKey().get().createParentKey(); - if (!localQuery.tileKey()->valid()) - { - // We fell back all the way to lod 0 and got nothing, so bail. - break; - } - } - else - { - // Just bail, we didn't get any features and aren't using tilekeys - break; - } - } - } -} diff --git a/src/osgEarth/FeatureSDFLayer.cpp b/src/osgEarth/FeatureSDFLayer.cpp index 038ddcfb03..ff63784f7d 100644 --- a/src/osgEarth/FeatureSDFLayer.cpp +++ b/src/osgEarth/FeatureSDFLayer.cpp @@ -18,6 +18,7 @@ */ #include "FeatureSDFLayer" #include "FeatureRasterizer" +#include "FeatureStyleSorter" #include #include diff --git a/src/osgEarth/FeatureStyleSorter b/src/osgEarth/FeatureStyleSorter new file mode 100644 index 0000000000..9e58333f75 --- /dev/null +++ b/src/osgEarth/FeatureStyleSorter @@ -0,0 +1,86 @@ +/* -*-c++-*- */ +/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph + * Copyright 2020 Pelican Mapping + * http://osgearth.org + * + * osgEarth is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see + */ +#pragma once + +#include +#include +#include +#include + +namespace osgEarth +{ + /** + * Sorts input features by style and then runs a user-supplied + * function on each group of features that share the same style. + */ + class OSGEARTH_EXPORT FeatureStyleSorter + { + public: + FeatureStyleSorter() = default; + + using Function = std::function; + + void sort( + const TileKey& key, + const Distance& buffer, + Session* session, + const FeatureFilterChain& filters, + Function processFeaturesForStyle, + ProgressCallback* progress) const; + + protected: + void getFeatures( + Session* session, + const Query& query, + const Distance& buffer, + const GeoExtent& extent, + const FeatureFilterChain& filters, + FeatureList& output, + ProgressCallback* progress) const; + + void sort_usingEmbeddedStyles( + const TileKey& key, + const Distance& buffer, + const FeatureFilterChain& filters, + Session* session, + Function processFeaturesForStyle, + ProgressCallback* progress) const; + + void sort_usingSelectors( + const TileKey& key, + const Distance& buffer, + const FeatureFilterChain& filters, + Session* session, + Function processFeaturesForStyle, + ProgressCallback* progress) const; + + void sort_usingOneStyle( + const Style& style, + const TileKey& key, + const Distance& buffer, + const FeatureFilterChain& filters, + Session* session, + Function processFeaturesForStyle, + ProgressCallback* progress) const; + }; +} diff --git a/src/osgEarth/FeatureStyleSorter.cpp b/src/osgEarth/FeatureStyleSorter.cpp new file mode 100644 index 0000000000..76761fb7b3 --- /dev/null +++ b/src/osgEarth/FeatureStyleSorter.cpp @@ -0,0 +1,328 @@ +/* -*-c++-*- */ +/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph + * Copyright 2020 Pelican Mapping + * http://osgearth.org + * + * osgEarth is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see + */ +#include +#include +#include +#include +#include +#include + +using namespace osgEarth; + +void +FeatureStyleSorter::sort_usingEmbeddedStyles( + const TileKey& key, + const Distance& buffer, + const FeatureFilterChain& filters, + Session* session, + FeatureStyleSorter::Function processFeaturesForStyle, + ProgressCallback* progress) const +{ + // Each feature has its own embedded style data, so use that: + FilterContext context; + + Query query; + query.tileKey() = key; + query.buffer() = buffer; + + osg::ref_ptr cursor = session->getFeatureSource()->createFeatureCursor(query, filters, &context, progress); + + while (cursor.valid() && cursor->hasMore()) + { + auto feature = cursor->nextFeature(); + if (feature) + { + FeatureList data; + data.push_back(feature); + processFeaturesForStyle(feature->style().get(), data, progress); + } + } +} + +void +FeatureStyleSorter::sort_usingSelectors( + const TileKey& key, + const Distance& buffer, + const FeatureFilterChain& filters, + Session* session, + FeatureStyleSorter::Function processFeaturesForStyle, + ProgressCallback* progress) const +{ + FeatureSource* features = session->getFeatureSource(); + + Query defaultQuery; + defaultQuery.tileKey() = key; + + for (auto& iter : session->styles()->getSelectors()) + { + const StyleSelector& sel = iter.second; + if (sel.styleExpression().isSet()) + { + const FeatureProfile* featureProfile = features->getFeatureProfile(); + + // establish the working bounds and a context: + FilterContext context(session, featureProfile); + StringExpression styleExprCopy(sel.styleExpression().get()); + + FeatureList features; + getFeatures(session, defaultQuery, buffer, key.getExtent(), filters, features, progress); + if (!features.empty()) + { + std::unordered_map> literal_styles; + + // keep ordered. + std::map> style_buckets; + + for (FeatureList::iterator itr = features.begin(); itr != features.end(); ++itr) + { + Feature* feature = itr->get(); + + const std::string& styleString = feature->eval(styleExprCopy, &context); + if (!styleString.empty() && styleString != "null") + { + // resolve the style: + const Style* resolved_style = nullptr; + int resolved_index = 0; + + // if the style string begins with an open bracket, it's an inline style definition. + if (styleString.length() > 0 && styleString[0] == '{') + { + Config conf("style", styleString); + conf.setReferrer(sel.styleExpression().get().uriContext().referrer()); + conf.set("type", "text/css"); + auto& literal_style_and_index = literal_styles[conf.toJSON()]; + if (literal_style_and_index.first.empty()) + { + literal_style_and_index.first = Style(conf); + // literal styles always come AFTER sheet styles + literal_style_and_index.second = literal_styles.size() + session->styles()->getStyles().size(); + } + resolved_style = &literal_style_and_index.first; + resolved_index = literal_style_and_index.second; + } + + // otherwise, look up the style in the stylesheet. Do NOT fall back on a default + // style in this case: for style expressions, the user must be explicit about + // default styling; this is because there is no other way to exclude unwanted + // features. + else + { + auto style_and_index = session->styles()->getStyleAndIndex(styleString); + + //const Style* selected_style = session->styles()->getStyle(styleString, false); + if (style_and_index.first) + { + resolved_style = style_and_index.first; + resolved_index = style_and_index.second; + } + } + + if (resolved_style) + { + auto& bucket = style_buckets[resolved_index]; + bucket.first = resolved_style; + bucket.second.emplace_back(feature); + } + } + } + + // in order: + for (auto& iter : style_buckets) + { + const Style* style = iter.second.first; + FeatureList& list = iter.second.second; + processFeaturesForStyle(*style, list, progress); + } + } + } + else + { + const Style* style = session->styles()->getStyle(sel.getSelectedStyleName()); + Query query = sel.query().get(); + query.tileKey() = key; + + // Get the features + FeatureList features; + getFeatures(session, query, buffer, key.getExtent(), filters, features, progress); + + processFeaturesForStyle(*style, features, progress); + } + } +} + +void +FeatureStyleSorter::sort_usingOneStyle( + const Style& style, + const TileKey& key, + const Distance& buffer, + const FeatureFilterChain& filters, + Session* session, + Function processFeaturesForStyle, + ProgressCallback* progress) const +{ + Query defaultQuery; + defaultQuery.tileKey() = key; + + FeatureList features; + getFeatures(session, defaultQuery, buffer, key.getExtent(), filters, features, progress); + + processFeaturesForStyle(style, features, progress); +} + +void +FeatureStyleSorter::sort( + const TileKey& key, + const Distance& buffer, + Session* session, + const FeatureFilterChain& filters, + Function processFeaturesForStyle, + ProgressCallback* progress) const +{ + OE_SOFT_ASSERT_AND_RETURN(session, void()); + OE_SOFT_ASSERT_AND_RETURN(session->getFeatureSource(), void()); + OE_SOFT_ASSERT_AND_RETURN(session->getFeatureSource()->getFeatureProfile(), void()); + + if (session->getFeatureSource()->hasEmbeddedStyles()) + { + sort_usingEmbeddedStyles( + key, + buffer, + filters, + session, + processFeaturesForStyle, + progress); + + } + else if (session->styles()) + { + if (session->styles()->getSelectors().size() > 0) + { + sort_usingSelectors( + key, + buffer, + filters, + session, + processFeaturesForStyle, + progress); + + } + else + { + sort_usingOneStyle( + *session->styles()->getDefaultStyle(), + key, + buffer, + filters, + session, + processFeaturesForStyle, + progress); + } + } + else + { + sort_usingOneStyle( + Style(), // empty style + key, + buffer, + filters, + session, + processFeaturesForStyle, + progress); + } +} + +void +FeatureStyleSorter::getFeatures( + Session* session, + const Query& query, + const Distance& buffer, + const GeoExtent& workingExtent, + const FeatureFilterChain& filters, + FeatureList& features, + ProgressCallback* progress) const +{ + OE_SOFT_ASSERT_AND_RETURN(session != nullptr, void()); + OE_SOFT_ASSERT_AND_RETURN(session->getFeatureSource() != nullptr, void()); + OE_SOFT_ASSERT_AND_RETURN(session->getFeatureSource()->getFeatureProfile() != nullptr, void()); + OE_SOFT_ASSERT_AND_RETURN(workingExtent.isValid(), void()); + + // first we need the overall extent of the layer: + const GeoExtent& featuresExtent = session->getFeatureSource()->getFeatureProfile()->getExtent(); + + // convert them both to WGS84, intersect the extents, and convert back. + GeoExtent featuresExtentWGS84 = featuresExtent.transform(featuresExtent.getSRS()->getGeographicSRS()); + GeoExtent workingExtentWGS84 = workingExtent.transform(featuresExtent.getSRS()->getGeographicSRS()); + GeoExtent queryExtentWGS84 = featuresExtentWGS84.intersectionSameSRS(workingExtentWGS84); + if (queryExtentWGS84.isValid()) + { + GeoExtent queryExtent = queryExtentWGS84.transform(featuresExtent.getSRS()); + + // incorporate the image extent into the feature query for this style: + Query localQuery = query; + localQuery.bounds() = + query.bounds().isSet() ? unionOf(query.bounds().get(), queryExtent.bounds()) : + queryExtent.bounds(); + + FilterContext context(session, session->getFeatureSource()->getFeatureProfile(), queryExtent); + + // now copy the resulting feature set into a list, converting the data + // types along the way if a geometry override is in place: + while (features.empty()) + { + if (progress && progress->isCanceled()) + break; + + osg::ref_ptr cursor; + + if (localQuery.tileKey().isSet()) + { + localQuery.buffer() = buffer; + } + + cursor = session->getFeatureSource()->createFeatureCursor(localQuery, filters, &context, progress); + + while (cursor.valid() && cursor->hasMore()) + { + Feature* feature = cursor->nextFeature(); + if (feature->getGeometry()) + { + features.push_back(feature); + } + } + + // If we didn't get any features and we have a tilekey set, try falling back. + if (features.empty() && + localQuery.tileKey().isSet() && + localQuery.tileKey()->valid()) + { + localQuery.tileKey() = localQuery.tileKey().get().createParentKey(); + if (!localQuery.tileKey()->valid()) + { + // We fell back all the way to lod 0 and got nothing, so bail. + break; + } + } + else + { + // Just bail, we didn't get any features and aren't using tilekeys + break; + } + } + } +} diff --git a/src/osgEarthProcedural/RoadSurfaceLayer.cpp b/src/osgEarthProcedural/RoadSurfaceLayer.cpp index 335c5daf92..1ae7e51f95 100644 --- a/src/osgEarthProcedural/RoadSurfaceLayer.cpp +++ b/src/osgEarthProcedural/RoadSurfaceLayer.cpp @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include