Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgsalgorithmserviceareafrompoint.cpp
3 : : ---------------------
4 : : begin : July 2018
5 : : copyright : (C) 2018 by Alexander Bruy
6 : : email : alexander dot bruy at gmail dot com
7 : : ***************************************************************************/
8 : :
9 : : /***************************************************************************
10 : : * *
11 : : * This program is free software; you can redistribute it and/or modify *
12 : : * it under the terms of the GNU General Public License as published by *
13 : : * the Free Software Foundation; either version 2 of the License, or *
14 : : * (at your option) any later version. *
15 : : * *
16 : : ***************************************************************************/
17 : :
18 : : #include "qgsalgorithmserviceareafrompoint.h"
19 : :
20 : : #include "qgsgeometryutils.h"
21 : : #include "qgsgraphanalyzer.h"
22 : :
23 : : ///@cond PRIVATE
24 : :
25 : 0 : QString QgsServiceAreaFromPointAlgorithm::name() const
26 : : {
27 : 0 : return QStringLiteral( "serviceareafrompoint" );
28 : : }
29 : :
30 : 0 : QString QgsServiceAreaFromPointAlgorithm::displayName() const
31 : : {
32 : 0 : return QObject::tr( "Service area (from point)" );
33 : : }
34 : :
35 : 0 : QStringList QgsServiceAreaFromPointAlgorithm::tags() const
36 : : {
37 : 0 : return QObject::tr( "network,service,area,shortest,fastest" ).split( ',' );
38 : 0 : }
39 : :
40 : 0 : QString QgsServiceAreaFromPointAlgorithm::shortHelpString() const
41 : : {
42 : 0 : return QObject::tr( "This algorithm creates a new vector with all the edges or parts of edges "
43 : : "of a network line layer that can be reached within a distance or a time, "
44 : : "starting from a point feature. The distance and the time (both referred to "
45 : : "as \"travel cost\") must be specified respectively in the network layer "
46 : : "units or in hours." );
47 : : }
48 : :
49 : 0 : QgsServiceAreaFromPointAlgorithm *QgsServiceAreaFromPointAlgorithm::createInstance() const
50 : : {
51 : 0 : return new QgsServiceAreaFromPointAlgorithm();
52 : : }
53 : :
54 : 0 : void QgsServiceAreaFromPointAlgorithm::initAlgorithm( const QVariantMap & )
55 : : {
56 : 0 : addCommonParams();
57 : 0 : addParameter( new QgsProcessingParameterPoint( QStringLiteral( "START_POINT" ), QObject::tr( "Start point" ) ) );
58 : :
59 : 0 : std::unique_ptr< QgsProcessingParameterNumber > travelCost = std::make_unique< QgsProcessingParameterNumber >( QStringLiteral( "TRAVEL_COST" ), QObject::tr( "Travel cost (distance for 'Shortest', time for 'Fastest')" ), QgsProcessingParameterNumber::Double, 0, true, 0 );
60 : 0 : travelCost->setFlags( travelCost->flags() | QgsProcessingParameterDefinition::FlagHidden );
61 : 0 : addParameter( travelCost.release() );
62 : :
63 : 0 : addParameter( new QgsProcessingParameterNumber( QStringLiteral( "TRAVEL_COST2" ), QObject::tr( "Travel cost (distance for 'Shortest', time for 'Fastest')" ),
64 : 0 : QgsProcessingParameterNumber::Double, 0, false, 0 ) );
65 : :
66 : 0 : std::unique_ptr< QgsProcessingParameterBoolean > includeBounds = std::make_unique< QgsProcessingParameterBoolean >( QStringLiteral( "INCLUDE_BOUNDS" ), QObject::tr( "Include upper/lower bound points" ), false, true );
67 : 0 : includeBounds->setFlags( includeBounds->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
68 : 0 : addParameter( includeBounds.release() );
69 : :
70 : 0 : std::unique_ptr< QgsProcessingParameterFeatureSink > outputLines = std::make_unique< QgsProcessingParameterFeatureSink >( QStringLiteral( "OUTPUT_LINES" ), QObject::tr( "Service area (lines)" ),
71 : 0 : QgsProcessing::TypeVectorLine, QVariant(), true );
72 : 0 : outputLines->setCreateByDefault( true );
73 : 0 : addParameter( outputLines.release() );
74 : :
75 : 0 : std::unique_ptr< QgsProcessingParameterFeatureSink > outputPoints = std::make_unique< QgsProcessingParameterFeatureSink >( QStringLiteral( "OUTPUT" ), QObject::tr( "Service area (boundary nodes)" ),
76 : 0 : QgsProcessing::TypeVectorPoint, QVariant(), true );
77 : 0 : outputPoints->setCreateByDefault( false );
78 : 0 : addParameter( outputPoints.release() );
79 : 0 : }
80 : :
81 : 0 : QVariantMap QgsServiceAreaFromPointAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
82 : : {
83 : 0 : loadCommonParams( parameters, context, feedback );
84 : :
85 : 0 : QgsPointXY startPoint = parameterAsPoint( parameters, QStringLiteral( "START_POINT" ), context, mNetwork->sourceCrs() );
86 : :
87 : : // use older deprecated travel cost style if specified, to maintain old api
88 : 0 : const bool useOldTravelCost = parameters.value( QStringLiteral( "TRAVEL_COST" ) ).isValid();
89 : 0 : double travelCost = parameterAsDouble( parameters, useOldTravelCost ? QStringLiteral( "TRAVEL_COST" ) : QStringLiteral( "TRAVEL_COST2" ), context );
90 : :
91 : 0 : int strategy = parameterAsInt( parameters, QStringLiteral( "STRATEGY" ), context );
92 : 0 : if ( strategy && !useOldTravelCost )
93 : 0 : travelCost *= mMultiplier;
94 : :
95 : 0 : bool includeBounds = true; // default to true to maintain 3.0 API
96 : 0 : if ( parameters.contains( QStringLiteral( "INCLUDE_BOUNDS" ) ) )
97 : : {
98 : 0 : includeBounds = parameterAsBool( parameters, QStringLiteral( "INCLUDE_BOUNDS" ), context );
99 : 0 : }
100 : :
101 : 0 : feedback->pushInfo( QObject::tr( "Building graph…" ) );
102 : 0 : QVector< QgsPointXY > snappedPoints;
103 : 0 : mDirector->makeGraph( mBuilder.get(), QVector< QgsPointXY >() << startPoint, snappedPoints, feedback );
104 : :
105 : 0 : feedback->pushInfo( QObject::tr( "Calculating service area…" ) );
106 : 0 : QgsGraph *graph = mBuilder->graph();
107 : 0 : int idxStart = graph->findVertex( snappedPoints[0] );
108 : :
109 : 0 : QVector< int > tree;
110 : 0 : QVector< double > costs;
111 : 0 : QgsGraphAnalyzer::dijkstra( graph, idxStart, 0, &tree, &costs );
112 : :
113 : 0 : QgsMultiPointXY points;
114 : 0 : QgsMultiPolylineXY lines;
115 : 0 : QSet< int > vertices;
116 : :
117 : : int inboundEdgeIndex;
118 : : double startVertexCost, endVertexCost;
119 : 0 : QgsPointXY edgeStart, edgeEnd;
120 : 0 : QgsGraphEdge edge;
121 : :
122 : 0 : for ( int i = 0; i < costs.size(); i++ )
123 : : {
124 : 0 : inboundEdgeIndex = tree.at( i );
125 : 0 : if ( inboundEdgeIndex == -1 && i != idxStart )
126 : : {
127 : : // unreachable vertex
128 : 0 : continue;
129 : : }
130 : :
131 : 0 : startVertexCost = costs.at( i );
132 : 0 : if ( startVertexCost > travelCost )
133 : : {
134 : : // vertex is too expensive, discard
135 : 0 : continue;
136 : : }
137 : :
138 : 0 : vertices.insert( i );
139 : 0 : edgeStart = graph->vertex( i ).point();
140 : :
141 : : // find all edges coming from this vertex
142 : 0 : const QList< int > outgoingEdges = graph->vertex( i ).outgoingEdges() ;
143 : 0 : for ( int edgeId : outgoingEdges )
144 : : {
145 : 0 : edge = graph->edge( edgeId );
146 : 0 : endVertexCost = startVertexCost + edge.cost( 0 ).toDouble();
147 : 0 : edgeEnd = graph->vertex( edge.toVertex() ).point();
148 : 0 : if ( endVertexCost <= travelCost )
149 : : {
150 : : // end vertex is cheap enough to include
151 : 0 : vertices.insert( edge.toVertex() );
152 : 0 : lines.push_back( QgsPolylineXY() << edgeStart << edgeEnd );
153 : 0 : }
154 : : else
155 : : {
156 : : // travelCost sits somewhere on this edge, interpolate position
157 : 0 : QgsPointXY interpolatedEndPoint = QgsGeometryUtils::interpolatePointOnLineByValue( edgeStart.x(), edgeStart.y(), startVertexCost,
158 : 0 : edgeEnd.x(), edgeEnd.y(), endVertexCost, travelCost );
159 : :
160 : 0 : points.push_back( interpolatedEndPoint );
161 : 0 : lines.push_back( QgsPolylineXY() << edgeStart << interpolatedEndPoint );
162 : : }
163 : : } // edges
164 : 0 : } // costs
165 : :
166 : : // convert to list and sort to maintain same order of points between algorithm runs
167 : 0 : QList< int > verticesList = qgis::setToList( vertices );
168 : 0 : points.reserve( verticesList.size() );
169 : 0 : std::sort( verticesList.begin(), verticesList.end() );
170 : 0 : for ( int v : verticesList )
171 : : {
172 : 0 : points.push_back( graph->vertex( v ).point() );
173 : : }
174 : :
175 : 0 : feedback->pushInfo( QObject::tr( "Writing results…" ) );
176 : :
177 : 0 : QVariantMap outputs;
178 : :
179 : 0 : QgsFields fields;
180 : 0 : fields.append( QgsField( QStringLiteral( "type" ), QVariant::String ) );
181 : 0 : fields.append( QgsField( QStringLiteral( "start" ), QVariant::String ) );
182 : :
183 : 0 : QgsFeature feat;
184 : 0 : feat.setFields( fields );
185 : :
186 : 0 : QString pointsSinkId;
187 : 0 : std::unique_ptr< QgsFeatureSink > pointsSink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, pointsSinkId, fields,
188 : 0 : QgsWkbTypes::MultiPoint, mNetwork->sourceCrs() ) );
189 : :
190 : 0 : if ( pointsSink )
191 : : {
192 : 0 : outputs.insert( QStringLiteral( "OUTPUT" ), pointsSinkId );
193 : :
194 : 0 : QgsGeometry geomPoints = QgsGeometry::fromMultiPointXY( points );
195 : 0 : feat.setGeometry( geomPoints );
196 : 0 : feat.setAttributes( QgsAttributes() << QStringLiteral( "within" ) << startPoint.toString() );
197 : 0 : pointsSink->addFeature( feat, QgsFeatureSink::FastInsert );
198 : :
199 : 0 : if ( includeBounds )
200 : : {
201 : 0 : QgsMultiPointXY upperBoundary, lowerBoundary;
202 : 0 : QVector< int > nodes;
203 : :
204 : : int vertexId;
205 : 0 : for ( int i = 0; i < costs.size(); i++ )
206 : : {
207 : 0 : if ( costs.at( i ) > travelCost && tree.at( i ) != -1 )
208 : : {
209 : 0 : vertexId = graph->edge( tree.at( i ) ).fromVertex();
210 : 0 : if ( costs.at( vertexId ) <= travelCost )
211 : : {
212 : 0 : nodes.push_back( i );
213 : 0 : }
214 : 0 : }
215 : 0 : } // costs
216 : :
217 : 0 : for ( int i : nodes )
218 : : {
219 : 0 : upperBoundary.push_back( graph->vertex( graph->edge( tree.at( i ) ).toVertex() ).point() );
220 : 0 : lowerBoundary.push_back( graph->vertex( graph->edge( tree.at( i ) ).fromVertex() ).point() );
221 : : } // nodes
222 : :
223 : 0 : QgsGeometry geomUpper = QgsGeometry::fromMultiPointXY( upperBoundary );
224 : 0 : QgsGeometry geomLower = QgsGeometry::fromMultiPointXY( lowerBoundary );
225 : :
226 : 0 : feat.setGeometry( geomUpper );
227 : 0 : feat.setAttributes( QgsAttributes() << QStringLiteral( "upper" ) << startPoint.toString() );
228 : 0 : pointsSink->addFeature( feat, QgsFeatureSink::FastInsert );
229 : :
230 : 0 : feat.setGeometry( geomLower );
231 : 0 : feat.setAttributes( QgsAttributes() << QStringLiteral( "lower" ) << startPoint.toString() );
232 : 0 : pointsSink->addFeature( feat, QgsFeatureSink::FastInsert );
233 : 0 : } // includeBounds
234 : 0 : }
235 : :
236 : 0 : QString linesSinkId;
237 : 0 : std::unique_ptr< QgsFeatureSink > linesSink( parameterAsSink( parameters, QStringLiteral( "OUTPUT_LINES" ), context, linesSinkId, fields,
238 : 0 : QgsWkbTypes::MultiLineString, mNetwork->sourceCrs() ) );
239 : :
240 : 0 : if ( linesSink )
241 : : {
242 : 0 : outputs.insert( QStringLiteral( "OUTPUT_LINES" ), linesSinkId );
243 : 0 : QgsGeometry geomLines = QgsGeometry::fromMultiPolylineXY( lines );
244 : 0 : feat.setGeometry( geomLines );
245 : 0 : feat.setAttributes( QgsAttributes() << QStringLiteral( "lines" ) << startPoint.toString() );
246 : 0 : linesSink->addFeature( feat, QgsFeatureSink::FastInsert );
247 : 0 : }
248 : :
249 : 0 : return outputs;
250 : 0 : }
251 : :
252 : : ///@endcond
|