Skip to content

Commit

Permalink
[feature][processing] New algorithm "Align points to features"
Browse files Browse the repository at this point in the history
This algorithm calculates the rotation required to align point features
with their nearest feature from another reference layer. A new field is
added to the output layer which is filled with the angle (in degrees,
clockwise) to the nearest reference feature.

Optionally, the output layer's symbology can be set to automatically
use the calculated rotation field to rotate marker symbols.

If desired, a maximum distance to use when aligning points can be set,
to avoid aligning isolated points to distant features.

Designed for use cases like aligning building point symbols to follow
the nearest road direction!
  • Loading branch information
nyalldawson committed Jul 25, 2020
1 parent 9b19e06 commit 95bd7b2
Show file tree
Hide file tree
Showing 9 changed files with 624 additions and 0 deletions.
@@ -0,0 +1,86 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://ogr.maptools.org/ rotated_points_max.xsd"
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy>
<gml:Box>
<gml:coord><gml:X>0</gml:X><gml:Y>-5</gml:Y></gml:coord>
<gml:coord><gml:X>8</gml:X><gml:Y>3</gml:Y></gml:coord>
</gml:Box>
</gml:boundedBy>

<gml:featureMember>
<ogr:rotated_points_max fid="points.0">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>1,1</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>1</ogr:id>
<ogr:id2>2</ogr:id2>
<ogr:angle>85.3649325292524</ogr:angle>
</ogr:rotated_points_max>
</gml:featureMember>
<gml:featureMember>
<ogr:rotated_points_max fid="points.1">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>3,3</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>2</ogr:id>
<ogr:id2>1</ogr:id2>
<ogr:angle xsi:nil="true"/>
</ogr:rotated_points_max>
</gml:featureMember>
<gml:featureMember>
<ogr:rotated_points_max fid="points.2">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>2,2</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>3</ogr:id>
<ogr:id2>0</ogr:id2>
<ogr:angle>6.7420642833774</ogr:angle>
</ogr:rotated_points_max>
</gml:featureMember>
<gml:featureMember>
<ogr:rotated_points_max fid="points.3">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>5,2</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>4</ogr:id>
<ogr:id2>2</ogr:id2>
<ogr:angle xsi:nil="true"/>
</ogr:rotated_points_max>
</gml:featureMember>
<gml:featureMember>
<ogr:rotated_points_max fid="points.4">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>4,1</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>5</ogr:id>
<ogr:id2>1</ogr:id2>
<ogr:angle xsi:nil="true"/>
</ogr:rotated_points_max>
</gml:featureMember>
<gml:featureMember>
<ogr:rotated_points_max fid="points.5">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>0,-5</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>6</ogr:id>
<ogr:id2>0</ogr:id2>
<ogr:angle xsi:nil="true"/>
</ogr:rotated_points_max>
</gml:featureMember>
<gml:featureMember>
<ogr:rotated_points_max fid="points.6">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>8,-1</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>7</ogr:id>
<ogr:id2>0</ogr:id2>
<ogr:angle>124.307787844716</ogr:angle>
</ogr:rotated_points_max>
</gml:featureMember>
<gml:featureMember>
<ogr:rotated_points_max fid="points.7">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>7,-1</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>8</ogr:id>
<ogr:id2>0</ogr:id2>
<ogr:angle>147.639207132861</ogr:angle>
</ogr:rotated_points_max>
</gml:featureMember>
<gml:featureMember>
<ogr:rotated_points_max fid="points.8">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>0,-1</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>9</ogr:id>
<ogr:id2>0</ogr:id2>
<ogr:angle>180</ogr:angle>
</ogr:rotated_points_max>
</gml:featureMember>
</ogr:FeatureCollection>
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema targetNamespace="http://ogr.maptools.org/" xmlns:ogr="http://ogr.maptools.org/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:gml="http://www.opengis.net/gml" elementFormDefault="qualified" version="1.0">
<xs:import namespace="http://www.opengis.net/gml" schemaLocation="http://schemas.opengis.net/gml/2.1.2/feature.xsd"/>
<xs:element name="FeatureCollection" type="ogr:FeatureCollectionType" substitutionGroup="gml:_FeatureCollection"/>
<xs:complexType name="FeatureCollectionType">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureCollectionType">
<xs:attribute name="lockId" type="xs:string" use="optional"/>
<xs:attribute name="scope" type="xs:string" use="optional"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:element name="rotated_points_max" type="ogr:rotated_points_max_Type" substitutionGroup="gml:_Feature"/>
<xs:complexType name="rotated_points_max_Type">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureType">
<xs:sequence>
<xs:element name="geometryProperty" type="gml:PointPropertyType" nillable="true" minOccurs="0" maxOccurs="1"/>
<xs:element name="id" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:totalDigits value="10"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="id2" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:totalDigits value="10"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="angle" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:decimal">
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:schema>
@@ -0,0 +1,86 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://ogr.maptools.org/ rotated_points_no_max.xsd"
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy>
<gml:Box>
<gml:coord><gml:X>0</gml:X><gml:Y>-5</gml:Y></gml:coord>
<gml:coord><gml:X>8</gml:X><gml:Y>3</gml:Y></gml:coord>
</gml:Box>
</gml:boundedBy>

<gml:featureMember>
<ogr:rotated_points_no_max fid="points.0">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>1,1</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>1</ogr:id>
<ogr:id2>2</ogr:id2>
<ogr:rotation>85.3649325292524</ogr:rotation>
</ogr:rotated_points_no_max>
</gml:featureMember>
<gml:featureMember>
<ogr:rotated_points_no_max fid="points.1">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>3,3</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>2</ogr:id>
<ogr:id2>1</ogr:id2>
<ogr:rotation>-165.374165295877</ogr:rotation>
</ogr:rotated_points_no_max>
</gml:featureMember>
<gml:featureMember>
<ogr:rotated_points_no_max fid="points.2">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>2,2</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>3</ogr:id>
<ogr:id2>0</ogr:id2>
<ogr:rotation>6.7420642833774</ogr:rotation>
</ogr:rotated_points_no_max>
</gml:featureMember>
<gml:featureMember>
<ogr:rotated_points_no_max fid="points.3">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>5,2</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>4</ogr:id>
<ogr:id2>2</ogr:id2>
<ogr:rotation>72.7061456490137</ogr:rotation>
</ogr:rotated_points_no_max>
</gml:featureMember>
<gml:featureMember>
<ogr:rotated_points_no_max fid="points.4">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>4,1</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>5</ogr:id>
<ogr:id2>1</ogr:id2>
<ogr:rotation>-41.558194492138</ogr:rotation>
</ogr:rotated_points_no_max>
</gml:featureMember>
<gml:featureMember>
<ogr:rotated_points_no_max fid="points.5">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>0,-5</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>6</ogr:id>
<ogr:id2>0</ogr:id2>
<ogr:rotation>90</ogr:rotation>
</ogr:rotated_points_no_max>
</gml:featureMember>
<gml:featureMember>
<ogr:rotated_points_no_max fid="points.6">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>8,-1</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>7</ogr:id>
<ogr:id2>0</ogr:id2>
<ogr:rotation>124.307787844716</ogr:rotation>
</ogr:rotated_points_no_max>
</gml:featureMember>
<gml:featureMember>
<ogr:rotated_points_no_max fid="points.7">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>7,-1</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>8</ogr:id>
<ogr:id2>0</ogr:id2>
<ogr:rotation>147.639207132861</ogr:rotation>
</ogr:rotated_points_no_max>
</gml:featureMember>
<gml:featureMember>
<ogr:rotated_points_no_max fid="points.8">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>0,-1</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>9</ogr:id>
<ogr:id2>0</ogr:id2>
<ogr:rotation>180</ogr:rotation>
</ogr:rotated_points_no_max>
</gml:featureMember>
</ogr:FeatureCollection>
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema targetNamespace="http://ogr.maptools.org/" xmlns:ogr="http://ogr.maptools.org/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:gml="http://www.opengis.net/gml" elementFormDefault="qualified" version="1.0">
<xs:import namespace="http://www.opengis.net/gml" schemaLocation="http://schemas.opengis.net/gml/2.1.2/feature.xsd"/>
<xs:element name="FeatureCollection" type="ogr:FeatureCollectionType" substitutionGroup="gml:_FeatureCollection"/>
<xs:complexType name="FeatureCollectionType">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureCollectionType">
<xs:attribute name="lockId" type="xs:string" use="optional"/>
<xs:attribute name="scope" type="xs:string" use="optional"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:element name="rotated_points_no_max" type="ogr:rotated_points_no_max_Type" substitutionGroup="gml:_Feature"/>
<xs:complexType name="rotated_points_no_max_Type">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureType">
<xs:sequence>
<xs:element name="geometryProperty" type="gml:PointPropertyType" nillable="true" minOccurs="0" maxOccurs="1"/>
<xs:element name="id" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:totalDigits value="10"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="id2" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:totalDigits value="10"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="rotation" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:decimal">
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:schema>
Expand Up @@ -2501,5 +2501,46 @@ tests:
hash: 63610fb1f7ec3d6530591b1eb479355b52cedfebf6331cec77f2b35c
type: rasterhash

- algorithm: native:angletonearest
name: Align points to nearest line, no max distance
params:
APPLY_SYMBOLOGY: true
FIELD_NAME: rotation
INPUT:
name: points.gml|layername=points
type: vector
REFERENCE_LAYER:
name: custom/snap_lines_3857.gml|layername=snap_lines_3857
type: vector
results:
OUTPUT:
name: expected/rotated_points_no_max.gml
type: vector
compare:
fields:
rotation:
precision: 2

- algorithm: native:angletonearest
name: Align points to nearest line, max distance
params:
APPLY_SYMBOLOGY: true
FIELD_NAME: angle
INPUT:
name: points.gml|layername=points
type: vector
MAX_DISTANCE: 1.0
REFERENCE_LAYER:
name: custom/snap_lines_3857.gml|layername=snap_lines_3857
type: vector
results:
OUTPUT:
name: expected/rotated_points_max.gml
type: vector
compare:
fields:
angle:
precision: 2


# See ../README.md for a description of the file format
1 change: 1 addition & 0 deletions src/analysis/CMakeLists.txt
Expand Up @@ -26,6 +26,7 @@ SET(QGIS_ANALYSIS_SRCS
processing/qgsalgorithmaddxyfields.cpp
processing/qgsalgorithmaffinetransform.cpp
processing/qgsalgorithmaggregate.cpp
processing/qgsalgorithmangletonearest.cpp
processing/qgsalgorithmapplylayerstyle.cpp
processing/qgsalgorithmarraytranslatedfeatures.cpp
processing/qgsalgorithmaspect.cpp
Expand Down

0 comments on commit 95bd7b2

Please sign in to comment.