Skip to content
Permalink
Browse files

[feature][processing] New algorithm "Align points to features"

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 95bd7b296bd6516fb09182da5ec4c74f5b962d93
@@ -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>
@@ -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
@@ -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

0 comments on commit 95bd7b2

Please sign in to comment.
You can’t perform that action at this time.