Skip to content

Commit fa2c5ae

Browse files
committed
[FEATURE][processing] Import geotagged photos algorithm
This implements a new "import geotagged photos" algorithm for processing. It allows selection of a folder which it will scan for jpg files which have been geotagged and creates a PointZ layer with the result, with attributes for photo path, altitude, direction and timestamp. Optionally the scan can be recursive and you can create an optional table of photos which could not be read or which were missing geotags. The algorithm automatically sets the output table to use an external resource widget to display the linked photos in the attribute form. [ALGCHANGE]
1 parent 87e8432 commit fa2c5ae

20 files changed

+832
-23
lines changed

python/core/raster/qgsrasterdataprovider.sip.in

-1
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,6 @@ Copy member variables from other raster data provider. Useful for implementation
440440

441441

442442

443-
444443
};
445444

446445
QFlags<QgsRasterDataProvider::ProviderCapability> operator|(QgsRasterDataProvider::ProviderCapability f1, QFlags<QgsRasterDataProvider::ProviderCapability> f2);
Loading
Loading
Loading
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<ogr:FeatureCollection
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://ogr.maptools.org/ import_photos.xsd"
5+
xmlns:ogr="http://ogr.maptools.org/"
6+
xmlns:gml="http://www.opengis.net/gml">
7+
<gml:boundedBy>
8+
<gml:Box>
9+
<gml:coord><gml:X>149.1318777777778</gml:X><gml:Y>-37.2305</gml:Y><gml:Z>422.191</gml:Z></gml:coord>
10+
<gml:coord><gml:X>149.2751666666667</gml:X><gml:Y>-36.22089166666667</gml:Y><gml:Z>867</gml:Z></gml:coord>
11+
</gml:Box>
12+
</gml:boundedBy>
13+
14+
<gml:featureMember>
15+
<ogr:import_photos fid="import_photos.0">
16+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>149.275166666667,-37.2305,422.191</gml:coordinates></gml:Point></ogr:geometryProperty>
17+
<ogr:photo>/home/nyall/dev/QGIS/python/plugins/processing/tests/testdata/custom/photos/0997.JPG</ogr:photo>
18+
<ogr:filename>0997</ogr:filename>
19+
<ogr:directory>/home/nyall/dev/QGIS/python/plugins/processing/tests/testdata/custom/photos</ogr:directory>
20+
<ogr:altitude>422.191</ogr:altitude>
21+
<ogr:direction>59.9153</ogr:direction>
22+
<ogr:longitude>149.2751666666667</ogr:longitude>
23+
<ogr:latitude>-37.2305</ogr:latitude>
24+
<ogr:timestamp>2012/03/06 14:28:40</ogr:timestamp>
25+
</ogr:import_photos>
26+
</gml:featureMember>
27+
<gml:featureMember>
28+
<ogr:import_photos fid="import_photos.1">
29+
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>149.131877777778,-36.2208916666667,867</gml:coordinates></gml:Point></ogr:geometryProperty>
30+
<ogr:photo>/home/nyall/dev/QGIS/python/plugins/processing/tests/testdata/custom/photos/geotagged.jpg</ogr:photo>
31+
<ogr:filename>geotagged</ogr:filename>
32+
<ogr:directory>/home/nyall/dev/QGIS/python/plugins/processing/tests/testdata/custom/photos</ogr:directory>
33+
<ogr:altitude>867</ogr:altitude>
34+
<ogr:longitude>149.1318777777778</ogr:longitude>
35+
<ogr:latitude>-36.22089166666667</ogr:latitude>
36+
<ogr:timestamp>2017/12/27 19:20:52</ogr:timestamp>
37+
</ogr:import_photos>
38+
</gml:featureMember>
39+
</ogr:FeatureCollection>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<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">
3+
<xs:import namespace="http://www.opengis.net/gml" schemaLocation="http://schemas.opengis.net/gml/2.1.2/feature.xsd"/>
4+
<xs:element name="FeatureCollection" type="ogr:FeatureCollectionType" substitutionGroup="gml:_FeatureCollection"/>
5+
<xs:complexType name="FeatureCollectionType">
6+
<xs:complexContent>
7+
<xs:extension base="gml:AbstractFeatureCollectionType">
8+
<xs:attribute name="lockId" type="xs:string" use="optional"/>
9+
<xs:attribute name="scope" type="xs:string" use="optional"/>
10+
</xs:extension>
11+
</xs:complexContent>
12+
</xs:complexType>
13+
<xs:element name="import_photos" type="ogr:import_photos_Type" substitutionGroup="gml:_Feature"/>
14+
<xs:complexType name="import_photos_Type">
15+
<xs:complexContent>
16+
<xs:extension base="gml:AbstractFeatureType">
17+
<xs:sequence>
18+
<xs:element name="geometryProperty" type="gml:PointPropertyType" nillable="true" minOccurs="0" maxOccurs="1"/>
19+
<xs:element name="photo" nillable="true" minOccurs="0" maxOccurs="1">
20+
<xs:simpleType>
21+
<xs:restriction base="xs:string">
22+
<xs:maxLength value="255"/>
23+
</xs:restriction>
24+
</xs:simpleType>
25+
</xs:element>
26+
<xs:element name="filename" nillable="true" minOccurs="0" maxOccurs="1">
27+
<xs:simpleType>
28+
<xs:restriction base="xs:string">
29+
<xs:maxLength value="255"/>
30+
</xs:restriction>
31+
</xs:simpleType>
32+
</xs:element>
33+
<xs:element name="directory" nillable="true" minOccurs="0" maxOccurs="1">
34+
<xs:simpleType>
35+
<xs:restriction base="xs:string">
36+
<xs:maxLength value="255"/>
37+
</xs:restriction>
38+
</xs:simpleType>
39+
</xs:element>
40+
<xs:element name="altitude" nillable="true" minOccurs="0" maxOccurs="1">
41+
<xs:simpleType>
42+
<xs:restriction base="xs:decimal">
43+
</xs:restriction>
44+
</xs:simpleType>
45+
</xs:element>
46+
<xs:element name="direction" nillable="true" minOccurs="0" maxOccurs="1">
47+
<xs:simpleType>
48+
<xs:restriction base="xs:decimal">
49+
</xs:restriction>
50+
</xs:simpleType>
51+
</xs:element>
52+
<xs:element name="longitude" nillable="true" minOccurs="0" maxOccurs="1">
53+
<xs:simpleType>
54+
<xs:restriction base="xs:string">
55+
<xs:maxLength value="255"/>
56+
</xs:restriction>
57+
</xs:simpleType>
58+
</xs:element>
59+
<xs:element name="latitude" nillable="true" minOccurs="0" maxOccurs="1">
60+
<xs:simpleType>
61+
<xs:restriction base="xs:string">
62+
<xs:maxLength value="255"/>
63+
</xs:restriction>
64+
</xs:simpleType>
65+
</xs:element>
66+
<xs:element name="timestamp" nillable="true" minOccurs="0" maxOccurs="1">
67+
<xs:simpleType>
68+
<xs:restriction base="xs:string">
69+
</xs:restriction>
70+
</xs:simpleType>
71+
</xs:element>
72+
</xs:sequence>
73+
</xs:extension>
74+
</xs:complexContent>
75+
</xs:complexType>
76+
</xs:schema>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<ogr:FeatureCollection
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://ogr.maptools.org/ import_photos_invalid.xsd"
5+
xmlns:ogr="http://ogr.maptools.org/"
6+
xmlns:gml="http://www.opengis.net/gml">
7+
<gml:boundedBy><gml:null>missing</gml:null></gml:boundedBy>
8+
9+
<gml:featureMember>
10+
<ogr:import_photos_invalid fid="import_photos_invalid.0">
11+
<ogr:photo>/home/nyall/dev/QGIS/python/plugins/processing/tests/testdata/custom/photos/not_photo.jpg</ogr:photo>
12+
<ogr:filename>not_photo</ogr:filename>
13+
<ogr:directory>/home/nyall/dev/QGIS/python/plugins/processing/tests/testdata/custom/photos</ogr:directory>
14+
<ogr:readable>false</ogr:readable>
15+
</ogr:import_photos_invalid>
16+
</gml:featureMember>
17+
<gml:featureMember>
18+
<ogr:import_photos_invalid fid="import_photos_invalid.1">
19+
<ogr:photo>/home/nyall/dev/QGIS/python/plugins/processing/tests/testdata/custom/photos/notags.JPG</ogr:photo>
20+
<ogr:filename>notags</ogr:filename>
21+
<ogr:directory>/home/nyall/dev/QGIS/python/plugins/processing/tests/testdata/custom/photos</ogr:directory>
22+
<ogr:readable>true</ogr:readable>
23+
</ogr:import_photos_invalid>
24+
</gml:featureMember>
25+
</ogr:FeatureCollection>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<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">
3+
<xs:import namespace="http://www.opengis.net/gml" schemaLocation="http://schemas.opengis.net/gml/2.1.2/feature.xsd"/>
4+
<xs:element name="FeatureCollection" type="ogr:FeatureCollectionType" substitutionGroup="gml:_FeatureCollection"/>
5+
<xs:complexType name="FeatureCollectionType">
6+
<xs:complexContent>
7+
<xs:extension base="gml:AbstractFeatureCollectionType">
8+
<xs:attribute name="lockId" type="xs:string" use="optional"/>
9+
<xs:attribute name="scope" type="xs:string" use="optional"/>
10+
</xs:extension>
11+
</xs:complexContent>
12+
</xs:complexType>
13+
<xs:element name="import_photos_invalid" type="ogr:import_photos_invalid_Type" substitutionGroup="gml:_Feature"/>
14+
<xs:complexType name="import_photos_invalid_Type">
15+
<xs:complexContent>
16+
<xs:extension base="gml:AbstractFeatureType">
17+
<xs:sequence>
18+
<xs:element name="photo" nillable="true" minOccurs="0" maxOccurs="1">
19+
<xs:simpleType>
20+
<xs:restriction base="xs:string">
21+
<xs:maxLength value="255"/>
22+
</xs:restriction>
23+
</xs:simpleType>
24+
</xs:element>
25+
<xs:element name="filename" nillable="true" minOccurs="0" maxOccurs="1">
26+
<xs:simpleType>
27+
<xs:restriction base="xs:string">
28+
<xs:maxLength value="255"/>
29+
</xs:restriction>
30+
</xs:simpleType>
31+
</xs:element>
32+
<xs:element name="directory" nillable="true" minOccurs="0" maxOccurs="1">
33+
<xs:simpleType>
34+
<xs:restriction base="xs:string">
35+
<xs:maxLength value="255"/>
36+
</xs:restriction>
37+
</xs:simpleType>
38+
</xs:element>
39+
<xs:element name="readable" nillable="true" minOccurs="0" maxOccurs="1">
40+
<xs:simpleType>
41+
<xs:restriction base="xs:boolean">
42+
</xs:restriction>
43+
</xs:simpleType>
44+
</xs:element>
45+
</xs:sequence>
46+
</xs:extension>
47+
</xs:complexContent>
48+
</xs:complexType>
49+
</xs:schema>

python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml

+15
Original file line numberDiff line numberDiff line change
@@ -4892,3 +4892,18 @@ tests:
48924892
OUTPUT:
48934893
name: expected/rotate_around_point.gml
48944894
type: vector
4895+
4896+
- algorithm: native:importphotos
4897+
name: Import photos
4898+
params:
4899+
FOLDER:
4900+
name: custom/photos
4901+
type: file
4902+
RECURSIVE: false
4903+
results:
4904+
INVALID:
4905+
name: expected/import_photos_invalid.gml
4906+
type: vector
4907+
OUTPUT:
4908+
name: expected/import_photos.gml
4909+
type: vector

src/analysis/CMakeLists.txt

+3
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ SET(QGIS_ANALYSIS_SRCS
4040
processing/qgsalgorithmfixgeometries.cpp
4141
processing/qgsalgorithmjoinbyattribute.cpp
4242
processing/qgsalgorithmjoinwithlines.cpp
43+
processing/qgsalgorithmimportphotos.cpp
4344
processing/qgsalgorithmlineintersection.cpp
4445
processing/qgsalgorithmloadlayer.cpp
4546
processing/qgsalgorithmmeancoordinates.cpp
@@ -202,6 +203,8 @@ QT5_WRAP_CPP(QGIS_ANALYSIS_MOC_SRCS ${QGIS_ANALYSIS_MOC_HDRS})
202203
# install headers
203204

204205
SET(QGIS_ANALYSIS_HDRS
206+
processing/qgsalgorithmimportphotos.h
207+
205208
raster/qgsalignraster.h
206209
raster/qgsaspectfilter.h
207210
raster/qgsderivativefilter.h

0 commit comments

Comments
 (0)