Skip to content

Commit

Permalink
Powerlines part 3, fix some math errors
Browse files Browse the repository at this point in the history
  • Loading branch information
gwaldron committed Jan 31, 2025
1 parent a6ccdf3 commit dc19548
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 92 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ set(OSGEARTH_PATCH_VERSION 1)
set(OSGEARTH_VERSION ${OSGEARTH_MAJOR_VERSION}.${OSGEARTH_MINOR_VERSION}.${OSGEARTH_PATCH_VERSION})

# Increment this each time the ABI changes
set(OSGEARTH_SOVERSION 165)
set(OSGEARTH_SOVERSION 166)

# Require C++14
set(CMAKE_CXX_STANDARD 14)
Expand Down
1 change: 0 additions & 1 deletion src/osgEarth/BuildGeometryFilter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,6 @@ BuildGeometryFilter::processPolygonizedLines(FeatureList& features,
{
polygonizer = new PolygonizeLinesOperator(line);
}
//GPULinesOperator gpuLines(*line->stroke() );

// iterate over all the feature's geometry parts. We will treat
// them as lines strings.
Expand Down
151 changes: 64 additions & 87 deletions src/osgEarth/PowerlineLayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,11 @@ namespace
}
else if (previous.x() != DBL_MAX && next.x() != DBL_MAX)
{
dir = next - previous;
auto a = point - previous;
auto b = next - point;
a.normalize();
b.normalize();
dir = a + b;
}
else if (previous.x() != DBL_MAX)
{
Expand Down Expand Up @@ -379,7 +383,7 @@ namespace
{
bool operator()(const osg::Vec3d& lhs, const osg::Vec3d& rhs) const
{
const double E = 5.0; // meters // 1e-5; // degrees
const double E = 0.1;
double dx = rhs.x() - lhs.x(), dy = rhs.y() - lhs.y();
if (dx < -E) return true;
if (dx > +E) return false;
Expand Down Expand Up @@ -494,7 +498,7 @@ namespace
}
}

// remove the onces that were null'd out during connection:
// remove the ones that were null'd out during connection:
FeatureList temp;
for (auto& feature : lines)
if (feature.valid())
Expand All @@ -511,15 +515,14 @@ namespace
GeometryIterator iter(feature->getGeometry(), false);
iter.forEach([&](Geometry* geom)
{
const int size = geom->size();
for (int i = 0; i < size; ++i)
for (int i = 0; i < geom->size(); ++i)
{
osg::Vec3d point = (*geom)[i];

// skip duplicates.
if (i > 0 && !eq2d(point, (*geom)[i - 1], 0.1)) // local data (mercator)
if (i == 0 || !eq2d(point, (*geom)[i - 1], 0.1)) // local data (mercator)
{
PointMap::iterator ptItr = pointMap.find(point);
auto ptItr = pointMap.find(point);
if (ptItr != pointMap.end())
{
PointEntry& entry = ptItr->second;
Expand All @@ -541,46 +544,35 @@ namespace
feature->setGeometry(output);
}
}
const SpatialReference* targetSRS = 0L;

const SpatialReference* targetSRS = nullptr;
if (context.getSession()->isMapGeocentric())
{
targetSRS = context.getSession()->getMapSRS();
}
else
{
targetSRS = context.profile()->getSRS()->getGeocentricSRS();
}

// calculate the heading of each tower/pole and record it to a feature attribute.
for (auto& i : pointMap)
{
PointEntry& entry = i.second;
Feature* pointFeature = entry.pointFeature.get();
if (!pointFeature)
{
Point* geom = new Point;
geom->set(i.first);
pointFeature = new Feature(geom, entry.lineFeatures.front()->getSRS(), Style(), 0L);
input.push_back(pointFeature);
}
entry.pointFeature->set("heading", calculateGeometryHeading(i.first, entry.previous, entry.next));
}

for (auto& lineFeature : entry.lineFeatures)
{
const AttributeTable& attrTable = lineFeature->getAttrs();
for (auto& attr_entry : attrTable)
{
if (attr_entry.first[0] != '@' && !pointFeature->hasAttr(attr_entry.first))
{
pointFeature->set(attr_entry.first, attr_entry.second);
}
}
// finally, remove any points that are not associated with a line.
FeatureList pointsWithLines;
pointsWithLines.reserve(points.size());
for(auto iter : pointMap)
{
if (!iter.second.lineFeatures.empty())
{
pointsWithLines.push_back(iter.second.pointFeature.get());
}
pointFeature->set("heading", calculateGeometryHeading(i.first, entry.previous, entry.next));
}

// combine all new features for output.
input.clear();
input.reserve(points.size() + lines.size());
input.insert(input.end(), points.begin(), points.end());
input.reserve(pointsWithLines.size() + lines.size());
input.insert(input.end(), pointsWithLines.begin(), pointsWithLines.end());
input.insert(input.end(), lines.begin(), lines.end());
}

Expand Down Expand Up @@ -787,10 +779,6 @@ namespace
{
modelSymbol->orientationFromFeature() = true;
}
//if (!altitudeSymbol->clamping().isSet() || force)
//{
// altitudeSymbol->clamping() = AltitudeSymbol::CLAMP_TO_TERRAIN;
//}
}

void setModelStyleDefaults(Style& modelStyle, const std::string& modelName, const std::string& referrer, bool force = true)
Expand All @@ -808,15 +796,8 @@ namespace

PowerlineFeatureNodeFactory::PowerlineFeatureNodeFactory(const PowerlineLayer::Options& options, StyleSheet* styles)
: GeomFeatureNodeFactory(options),
//_lineSourceLayer(options.lineSource().externalLayerName().get()),
//_point_features(options.point_features().get()),
_powerlineOptions(options)
{
//if (options.lineSource().embeddedOptions() != nullptr)
//{
// _lineSource = *options.lineSource().embeddedOptions();
//}

if (options.towerModels().empty())
{
OE_WARN << LC << "No tower models defined!" << std::endl;
Expand Down Expand Up @@ -903,13 +884,9 @@ PowerlineFeatureNodeFactory::makeCableFeatures(

const SpatialReference* targetSRS = nullptr;
if (cx.getSession()->isMapGeocentric())
{
targetSRS = cx.getSession()->getMapSRS();
}
else
{
targetSRS = featureSRS->getGeocentricSRS();
}

for(auto& feature : allPowerFeatures)
{
Expand All @@ -936,8 +913,8 @@ PowerlineFeatureNodeFactory::makeCableFeatures(
}
else
{
OE_NOTICE << LC << "tower not found!\n";
break;
OE_NOTICE << LC << "tower not found!" << std::endl;
//break;
}
}

Expand Down Expand Up @@ -975,11 +952,11 @@ PowerlineFeatureNodeFactory::makeCableFeatures(
{
// New feature for each cable
Feature* newFeature = new Feature(*feature);
LineString* newGeom = new LineString(size);
LineString* newGeom = new LineString(towerMats.size());
int currAttachment = startingAttachment;
std::vector<osg::Vec3d> cablePoints;
cablePoints.push_back(attachments(attachRow, currAttachment) * towerMats[0]);
for (int i = 1; i < size; ++i)
for (int i = 1; i < towerMats.size(); ++i)
{
// This function attempts to resolve the (rare) issue where lines cross between towers.
// While is does prevent crossing, it also has the side effect of putting all the
Expand All @@ -988,12 +965,6 @@ PowerlineFeatureNodeFactory::makeCableFeatures(
attachments(attachRow, currAttachment),
attachments(attachRow, 0), attachments(attachRow, 1));

// For now, Just ignore the result and pick the side we think it should be on.
// If we see too many crossed lines we can re-evaluate this.
// The solution may be to orient the towers' headings consistently (e.g., to that
// attachment 0 is always on the "west" side)
//next = startingAttachment;

osg::Vec3d worldAttach = attachments(attachRow, next) * towerMats[i];
cablePoints.push_back(worldAttach);
currAttachment = next;
Expand Down Expand Up @@ -1033,40 +1004,44 @@ PowerlineFeatureNodeFactory::makeCableFeatures(
return result;
}

class PredicateCursor : public FeatureCursor
namespace
{
public:
using Predicate = std::function<bool(Feature*)>;

PredicateCursor(FeatureCursor* cursor, Predicate predicate)
: FeatureCursor(), _cursor(cursor), _accept(predicate)
// todo: move this into FeatureCursor header
class PredicateCursor : public FeatureCursor
{
fetch();
}
public:
using Predicate = std::function<bool(Feature*)>;

bool hasMore() const override {
return _next.valid();
}
PredicateCursor(FeatureCursor* cursor, Predicate predicate)
: FeatureCursor(), _cursor(cursor), _accept(predicate)
{
fetch();
}

Feature* nextFeature() override {
osg::ref_ptr<Feature> result = _next.get();
fetch();
return result.release();
}
bool hasMore() const override {
return _next.valid();
}

void fetch() {
while (_cursor->hasMore()) {
_next = _cursor->nextFeature();
if (_accept(_next.get()))
return;
Feature* nextFeature() override {
osg::ref_ptr<Feature> result = _next.get();
fetch();
return result.release();
}
_next = nullptr;
}
private:
Predicate _accept;
osg::ref_ptr<FeatureCursor> _cursor;
osg::ref_ptr<Feature> _next;
};

void fetch() {
while (_cursor->hasMore()) {
_next = _cursor->nextFeature();
if (_accept(_next.get()))
return;
}
_next = nullptr;
}
private:
Predicate _accept;
osg::ref_ptr<FeatureCursor> _cursor;
osg::ref_ptr<Feature> _next;
};
}

bool PowerlineFeatureNodeFactory::createOrUpdateNode(
FeatureCursor* cursor,
Expand Down Expand Up @@ -1240,7 +1215,9 @@ bool PowerlineFeatureNodeFactory::createOrUpdateNode(

osg::ref_ptr<FeatureCursor> towersInExtentCursor = new PredicateCursor(
listCursor.get(),
[&](Feature* f) { return f->getExtent().intersects(localCX.extent().value()); });
[&](Feature* f) {
return localCX.extent()->intersects(f->getExtent());
});

// This has the side effect of updating the elevations of the point features according to the
// model style sheet. We rely on this in makeCableFeatures().
Expand Down
64 changes: 61 additions & 3 deletions tests/powerline.earth
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@

<map name="power tower test">

<PowerlineModel name="towers" max_range="6000" attenuation_range="1000">
<features>power-lines</features>
<filters>
<script>feature.properties.power === 'line' || feature.properties.power === 'tower';
<script>
feature.properties.power === 'line' ||
feature.properties.power === 'tower' ||
feature.properties.power === 'pole';
</script>
</filters>
<tower_model>
<uri>../data/power_tower.osgb</uri>
<attachment_points>
LINESTRING(6.400827 -0.031 26.068, -6.3926 -0.039 26.068, 3.981 -0.01 29.88, -3.854 0.39 29.89, 6.385 0.0 21.077, -6.35 -0.01 21.077)
LINESTRING(
6.400827 -0.031 26.068, -6.3926 -0.039 26.068,
3.981 -0.01 29.88, -3.854 0.39 29.89,
6.385 0.0 21.077, -6.35 -0.01 21.077)
</attachment_points>
<max_sag>5.0</max_sag>
</tower_model>
Expand All @@ -23,7 +30,7 @@
</styles>
</PowerlineModel>

<TiledFeatureModel name="power schematics" pickable="true" open="false">
<TiledFeatureModel name="schematics" pickable="true" open="false">
<features>power-lines</features>
<styles>
<style type="text/css">
Expand All @@ -42,6 +49,21 @@
point-size: 20px;
point-smooth: true;
}
poles {
select: feature.properties.power === 'pole';
altitude-clamping: terrain;
altitude-offset: 10;
point-fill: #00ffff;
point-size: 15px;
point-smooth: true;
}
minor_lines {
select: feature.properties.power === 'minor_line';
altitude-clamping: terrain;
altitude-offset: 8;
stroke: #007f7f;
stroke-width: 2px;
}
</style>
</styles>
</TiledFeatureModel>
Expand All @@ -61,6 +83,42 @@
</XYZFeatures>

<viewpoints time="0">
<viewpoint>
<heading>-0.585078</heading>
<pitch>-89.8521</pitch>
<range>378.104m</range>
<long>-83.16859651279468</long>
<lat>42.22142582488576</lat>
<height>140.7135450458154</height>
<srs>+proj=longlat +datum=WGS84 +no_defs</srs>
</viewpoint>
<viewpoint>
<heading>3.04123</heading>
<pitch>-89.8457</pitch>
<range>1680.92m</range>
<long>-117.3095814936931</long>
<lat>33.14499248796791</lat>
<height>-26.44459049869329</height>
<srs>+proj=longlat +datum=WGS84 +no_defs</srs>
</viewpoint>
<viewpoint>
<heading>-4.02148</heading>
<pitch>-36.2144</pitch>
<range>1687.27m</range>
<long>-117.3085710443049</long>
<lat>33.14211955140727</lat>
<height>-30.83710746653378</height>
<srs>+proj=longlat +datum=WGS84 +no_defs</srs>
</viewpoint>
<viewpoint name="Missing poles.">
<heading>10.2442</heading>
<pitch>-49.7483</pitch>
<range>1003.82m</range>
<long>-117.3298333666648</long>
<lat>33.20579918911835</lat>
<height>10.41661196015775</height>
<srs>+proj=longlat +datum=WGS84 +no_defs</srs>
</viewpoint>
<viewpoint name="Sion mystery lines">
<heading>72.5484</heading>
<pitch>-14.5443</pitch>
Expand Down

0 comments on commit dc19548

Please sign in to comment.