Side by side
| KML | GeoJSON | |
|---|---|---|
| What it is | XML, the OGC KML standard, written for Google Earth | JSON, RFC 7946, written for GIS and the web |
| Structure written here | One Document, one Placemark per photo | One FeatureCollection, one Point Feature per photo |
| Per-photo attributes | The filename, as the Placemark name. Nothing else | Nine properties: filename, altitude reference, yaw, pitch, roll, horizontal and vertical accuracy, RTK fix, capture time |
| Coordinate order | longitude, latitude, altitude | longitude, latitude, altitude |
| Altitude | Written as the third value, then ignored - no altitudeMode is set, so Google Earth clamps every pin to the terrain | Kept as the third element of the position array |
| Coordinate system | WGS 84 latitude and longitude, fixed by the format | WGS 84 longitude and latitude, fixed by RFC 7946, so no crs member is needed or allowed |
| Labels on screen | The filename appears beside each pin with no further work | Nothing is labelled until you style the layer |
| Best for | Showing a client, a pilot or a regulator where you flew | Anything you will filter, join, buffer or analyse |
| Weak point | Carries no attributes, so you cannot style or filter by RTK fix or height | No labels, no styling, and a reader who has never opened QGIS will not know what to do with it |
What each of the three files actually holds
The CSV has twelve columns in this order: filename, latitude, longitude, altitude_m, altitude_reference, yaw, pitch, roll, horizontal_accuracy_m, vertical_accuracy_m, rtk_fix and captured_utc. One row per photo with a position, a header row on top, and empty cells where a field was absent rather than a zero or the word null. Latitude and longitude are written to nine decimal places, the altitude to three.
Nine decimal places is about a tenth of a millimetre - one degree of latitude is roughly 111.32 km, so the ninth place is 0.11 mm on the ground. No drone position is good to that, RTK included; the places exist so the export never introduces a rounding error of its own, not as a claim about the fix.
The KML is deliberately plain: a single Document, and inside it one Placemark per photo carrying a name - the filename - and a Point. No style, no description balloon, no folder structure, no altitudeMode. The GeoJSON is a FeatureCollection of Point features, with the position as [longitude, latitude] or [longitude, latitude, altitude] when an altitude was readable, and the nine remaining fields as properties. Latitude and longitude are not repeated there, because in GeoJSON they live in the geometry.
All three are written from the same filtered list, with one rule worth knowing. On an image the parser classified as RTK, the position used is DJI's higher-precision XMP coordinate rather than the EXIF GPS one; on every other image it is the EXIF position. The two can differ: forum accounts of DJI airframes describe the EXIF GPS tags carrying fewer significant figures than the XMP pair, which is why the standing workaround on those threads is an ExifTool command copying the XMP values over the EXIF ones before processing.
Which file for which destination
KML for Google Earth, and for anyone who is not a GIS user. It opens by double-click in Google Earth Pro and in Google Earth on the web, the filenames appear beside the pins with no styling work, and the recipient needs to know nothing. If the question is 'show me where you flew', attach this one.
GeoJSON for QGIS, ArcGIS Pro, PostGIS and anything with a web map in it. Drag it onto the QGIS canvas and it loads as a point layer with the CRS already settled - RFC 7946 fixes GeoJSON to WGS 84 longitude-latitude, so there is no dialogue to get wrong - and you get the attribute table with it. That is the point: filter to the images whose rtk_fix is not 'fixed', symbolise by vertical accuracy, join the layer to your processing report. Google Earth's documentation now lists GeoJSON among the formats it imports, so the KML-only habit is out of date, though KML remains the one that opens everywhere.
CSV for a spreadsheet, for a photogrammetry package that wants a geotag list, and for the case where somebody downstream will do something you have not anticipated. It is the only one of the three a non-spatial tool can read, and the only one you can open, sort and hand-edit in thirty seconds. It is also the only one with a trap in it, which is the next section. There is deliberately no shapefile: the dbf attribute table truncates every field name to ten characters, so horizontal_accuracy_m and vertical_accuracy_m would arrive as the same unusable stub. If you need one, export it from QGIS, where you can rename the fields yourself.
The column-order trap when the CSV goes into QGIS
In the CSV, latitude is the second column and longitude the third. In QGIS's Add Delimited Text Layer dialogue, the X field must be longitude and the Y field must be latitude. Filling the boxes in the order the columns appear gives you X = latitude, Y = longitude, and the dialogue accepts it without a murmur.
The symptom is unmistakable once you know it. An Indian site at roughly 28 north, 77 east lands at 28 east, 77 north - in the Barents Sea, north of Norway. The classic version, for anywhere with small coordinate values, is the whole flight collapsing toward the Gulf of Guinea, where latitude zero meets longitude zero. If a 400-image flight sits in open water in a tidy little cluster, this is what happened and nothing is wrong with the file.
The second thing to set in that dialogue is the geometry CRS: EPSG:4326. The CSV carries no .prj and no embedded CRS, because a CSV has nowhere to put one. QGIS often defaults to the project CRS, and if your project is in a UTM zone it reads your degrees as metres and drops the survey a few hundred metres from the equator. If you would rather not face the dialogue at all, use the GeoJSON, where the format fixes both the axis order and the datum and neither failure is possible.
Read the altitude_reference column before you believe the altitude
The altitude_m column is one of two things. On an image the parser classified as RTK it is DJI's XMP AbsoluteAltitude, which travels with the precise XMP position, and altitude_reference reads absolute_xmp. On every other image it is whichever altitude the image carried, chosen in a fixed order: RelativeAltitude first if present, then EXIF GPSAltitude, then AbsoluteAltitude - so on a non-RTK DJI image the number is usually height above the take-off point and not elevation in any datum. The altitude_reference column records which one you got: relative, above_sea_level, below_sea_level, ellipsoid or unknown.
This has a consequence in the GeoJSON that the export does not resolve. RFC 7946 says the optional third element of a position 'SHALL be the height in meters above or below the WGS 84 reference ellipsoid'. A relative altitude is not that. It is height above wherever the aircraft left the ground, so two flights of one site from different launch points disagree with each other and with everything else. The altitude is written anyway, with altitude_reference beside it saying which field it came from. Read the two together rather than trusting the number alone: a 3D GeoJSON from this export conforms in shape, not in meaning. Treat a 3D GeoJSON from this export as conforming in shape and not in meaning until you have read that column.
In the KML the same problem is handled by not handling it. No altitudeMode element is written, and Google Earth's documented default for a feature without one is clampToGround, so every pin sits on the terrain and the third coordinate is ignored. That is the right behaviour here: a pin floating 120 m up because the drone reported height above take-off looks like a bug to every client who opens it.
If the vertical number is the deliverable rather than a byproduct, the ellipsoidal height versus mean sea level guide on this site covers the geoid, EGM96, EGM2008 and the three DJI altitude fields in full. The gap between a GNSS height and an elevation runs to tens of metres, is near-constant across one site and is invisible in plan view - which is why it ships.
When some of the photos have no GPS
The exports include only photos with a usable position. An image with no GPS block, or with a latitude and longitude the parser rejects as not a real fix, is left out of all three files, and nothing in the KML or the GeoJSON records that it existed.
The place that does record it is the readiness panel above the download buttons, which shows 'With GPS' as a count out of the total and warns how many images have no position. Check it before you export. The arithmetic works too: a CSV one row short of your image count, allowing for the header, means one photo was dropped.
The causes run from frames taken before the aircraft had a fix to metadata stripped by an editor or a messaging app, and the guide on photos with no GPS works through them in order. What matters here is only that the export is quietly shorter than the folder, so check the count before you hand the file on.
What to do depends on which. If the images were stripped in transit, go back to the card: no tool can reconstruct what is not there. If the drone logged a flight record with timestamps, the images can be geotagged from that log by time - a job for ExifTool or your flight software, not a browser. If only a handful at the start are affected, delete them and say so, because a photogrammetry run wants a position on every image or ground control instead.
What the export does not contain
It does not contain the photographs. All three files are positions and attributes, a few hundred bytes per image, and all of them are useless on their own if what you wanted was a map with pictures on it. KML's PhotoOverlay is the mechanism for that, and it is far more work than it sounds: each overlay needs an Icon pointing at the image, a ViewVolume with left, right, bottom and top field-of-view angles plus a near distance, a rotation, a Point, a shape and an ImagePyramid for anything large. You are reconstructing the camera, per photo, and the EXIF does not hand you those angles.
It does not contain footprints. There is no polygon of what each frame covered, no coverage or overlap layer and no flight path line joining the photos in order, because those need focal length and sensor size, the height above the terrain rather than above take-off, and a terrain model - the first half of photogrammetry, not a metadata export.
Nor does it contain everything the viewer read. Per-image GSD, camera make and model, lens settings, the full set of DJI XMP fields and the raw EXIF are all on screen and none of them are in these three files; the exports carry the fields that make a position useful in a GIS and stop there. The CSV is also not an ExifTool write-back file - it has a filename column, not the SourceFile column ExifTool's -csv= import requires.
What the other routes ask you to install
ExifTool is the standard answer and a genuinely excellent program, but it is a command line and the details bite. The incantation is roughly exiftool -csv -n -filename -gpslatitude -gpslongitude -gpsaltitude DIR > out.csv, and the -n matters more than it looks: without it ExifTool prints degrees, minutes and seconds, which no GIS will parse from a CSV. Its author has also had to explain on the forum, repeatedly, that with -n the raw GPSLatitude and GPSLongitude come out as unsigned magnitudes with the hemisphere in separate reference tags - so a southern or western site exports positive numbers and lands in the wrong quadrant unless you read the composite tags instead.
QGIS has the job built in, and more people should know it: Processing Toolbox, Vector creation, Import geotagged photos. Point it at a folder, optionally recursive, and it produces a PointZ layer in EPSG:4326 with nine attributes - photo, filename, directory, altitude, direction, rotation, longitude, latitude and timestamp - plus an optional table listing the files it could not read. It takes JPEG and HEIF or HEIC, and it is the right tool when the photos already sit on the machine running QGIS. What those nine fields do not include is anything from the vendor XMP: no RTK flag, no standard deviations, no gimbal pitch or roll, so you cannot tell a fixed solution from a single-point one in the attribute table.
The plugin route is heavier again. SkyStitch, the drone orthomosaic plugin in the QGIS repository, needs six Python packages - OpenCV, exifread, Rasterio, PyProj, SciPy and Pillow - installed into QGIS's own Python interpreter before it will run, and its 3D mode expects a NodeODM node on your machine too. A reasonable price for an orthomosaic; an absurd one for finding out where 400 photographs were taken.
The case for doing it here is narrow: you have the images, you want the positions, and you do not want to install anything or hand a client's imagery to a web service. The parsing runs in your browser and the download is a blob your own tab created, so there is no batch limit and no account. What this site cannot do is the processing afterwards - an orthomosaic needs real compute, which is a different product.
Questions people ask
How do I export drone photo GPS coordinates to a CSV?
Open the image metadata viewer on this site, drop in the whole folder of photos, and press CSV under 'Download photo positions'. You get one row per geotagged image with twelve columns: filename, latitude, longitude, altitude_m, altitude_reference, yaw, pitch, roll, horizontal_accuracy_m, vertical_accuracy_m, rtk_fix and captured_utc. The images are read in your browser and nothing is uploaded.
How do I import geotagged photos into QGIS?
Two ways. If the photos are in a folder on the same machine, use the built-in algorithm: Processing Toolbox, Vector creation, Import geotagged photos, which returns a PointZ layer in EPSG:4326 with photo, filename, directory, altitude, direction, rotation, longitude, latitude and timestamp. If you want the RTK fix type, the gimbal angles or the accuracy figures, export GeoJSON from the metadata viewer here instead and drag that onto the canvas, because those fields live in vendor XMP that the QGIS algorithm's output does not carry.
Why are my photo positions in the wrong place after loading the CSV in QGIS?
Almost always the X and Y fields are swapped. Latitude is the second column of the file and longitude the third, but QGIS needs X = longitude and Y = latitude, so filling the boxes in column order inverts every point. A site at 28 north, 77 east then appears at 28 east, 77 north, in the Barents Sea. The other cause is the geometry CRS: set it to EPSG:4326 explicitly, because a CSV carries no CRS of its own and QGIS may default to your project's.
Which format should I use to see my drone photos in Google Earth?
KML. It opens by double-click in Google Earth Pro and in Google Earth on the web, and each pin is labelled with its filename without any styling work. Google Earth also imports GeoJSON and zipped shapefiles now, but KML is the one that opens everywhere and the one to attach if the recipient is not a GIS user.
Does the KML show the actual photographs on the map?
No. All three exports contain positions and attributes, not imagery. Putting the pictures themselves into Google Earth means KML PhotoOverlay, which needs an Icon, a ViewVolume with four field-of-view angles and a near distance, a rotation, a Point and usually an ImagePyramid for each photo - effectively reconstructing the camera per image. The export gives you a labelled pin where each photo was taken.
Why does the altitude in my export look wrong?
Because it is usually not elevation. The altitude column prefers DJI's RelativeAltitude, which is height above the take-off point, falling back to EXIF GPSAltitude and then to AbsoluteAltitude. Read the altitude_reference column, which says which one you got: relative, above_sea_level, below_sea_level, ellipsoid or unknown. The KML sets no altitudeMode, so Google Earth clamps the pins to the terrain rather than floating them at a relative height.
Some of my photos are missing from the export. What happened?
Images without a usable GPS position are left out of all three files. The readiness panel above the download buttons shows the count with GPS against the total, so the number is visible before you export. The common causes are frames taken before the aircraft had a fix, images that went through a chat application or editor that stripped the metadata, and ground-level shots mixed into the folder. Stripped files cannot be recovered from the copy you have - go back to the card.