KML and KMZ: The Definitive Guide — Open, Convert, and Fix Errors
Someone sent you a .kmz from Google Earth and your GIS drew it in the middle of the ocean, or the placemarks arrived with every attribute gone. KML is the XML format Google Earth made universal; KMZ is the same file zipped, with its images. One rule sits under most failures on this page: <coordinates> are written longitude first — lon,lat[,alt], comma-separated, no spaces inside a tuple — and they are always WGS 84 decimal degrees. KML offers no other coordinate system, so there is nothing to declare and nothing to get wrong except the order.
Everything here was run, not copied, with two GDAL builds, because GDAL has two KML drivers that do not behave the same. Locally, GDAL 3.13.3 "Iowa City", where ogrinfo --formats | grep -i kml prints one line, KML -vector- (rw+v), and grep -c LIBKML prints 0. For LIBKML we used the OSGeo Docker image ghcr.io/osgeo/gdal:ubuntu-full-latest (digest sha256:98086f71…, GDAL 3.14.0dev of 2026/09/10), which has both; every LIBKML sentence below comes from a command run there with -if LIBKML. Nothing is written from a driver's documentation alone; the viewer claims were observed by loading the same files into the live page with a headless Chromium. Everything here was verified on 10 September 2026.
What KML is, and what KMZ adds
KML, Keyhole Markup Language, is XML from Keyhole, the company Google bought in 2004 to make Google Earth, and the format followed the product: it describes what to draw and how, not a dataset with a schema. It has been an OGC standard since 2008, 2.2 then 2.3, with opengis.net/kml/2.2 as the xmlns value; older files carry earth.google.com/kml/2.1 or 2.0, and the namespace entry under errors shows what GDAL does with each.
A minimal file is a <kml> root, a <Document>, and one <Placemark> with a <name> and a <Point> holding <coordinates>-46.63,-23.55,0</coordinates> — São Paulo, longitude first.
KMZ is that file inside a zip: a doc.kml at the root and, by convention, a files/ folder for the icons and overlay images the KML refers to by relative path. We ran ogr2ogr -f KML out.kml props.geojson on a two-point GeoJSON, zipped the result as doc.kml into out.kmz, and unzip -l out.kmz listed one entry, doc.kml, 1375 bytes. A KMZ written by ogr2ogr -f LIBKML out.kmz props.geojson holds doc.kml plus layers/props.kml, one file per layer. When a KMZ is huge, unzip -l is the first thing to run: the size is almost always in files/.
The structure that matters
Six elements account for nearly everything you will read, write or lose.
Placemark
One feature: a <name>, optional <description>, <styleUrl> and <ExtendedData>, and one geometry — <Point>, <LineString>, <Polygon> or a <MultiGeometry> wrapping several. A MultiGeometry may mix types, and GDAL reads it as one feature: our placemark holding a point and a line came back as GEOMETRYCOLLECTION Z (POINT Z (-46.63 -23.55 0),LINESTRING Z (-46.63 -23.55 0,-46.6 -23.5 0)) — legal in KML, GeoJSON and GeoPackage, impossible in a Shapefile.
Folder and Document
Document holds the shared things — styles and schemas — and Folder groups placemarks for the Google Earth sidebar; both nest. Both GDAL drivers read a folder as a layer: our <Document> with a Wells folder of two points and an Access roads folder of one line gave 1: Wells (3D Point) and 2: Access roads (3D Line String) from KML, 1: Wells and 2: Access roads from LIBKML, and ogr2ogr -f GPKG folders.gpkg folders.kml produced those two layers under either. Our viewer keeps them: 3 features · 1 Line · 2 Point · 2 folders, with Wells and Access roads in the layer panel and a folder attribute on every feature.
Style and StyleMap
A <Style id="siteNormal"> holds IconStyle, LineStyle, PolyStyle and BalloonStyle blocks; a placemark points at it with <styleUrl>#siteNormal</styleUrl>. A <StyleMap> pairs two styles under the keys normal and highlight for mouse-over. Colours are aabbggrr — alpha, blue, green, red — which is why a KML "red" reads ff0000ff. Styles survive no trip through GeoJSON, Shapefile or GeoPackage; the errors section has the counts.
ExtendedData and SchemaData
Attributes live in <ExtendedData>, in two dialects. The simple one is a list of <Data name="site_id"><value>42</value></Data> pairs, untyped strings. The typed one declares a <Schema> in the Document with <SimpleField name="site_id" type="int"/> entries and fills each placemark with <SchemaData schemaUrl="#props"><SimpleData name="site_id">42</SimpleData></SchemaData>. GDAL writes the typed dialect; what each driver reads back is covered under converting. Our viewer reads both: the Data pairs loaded as observation_notes and site_id with values Gate locked and 42, and the SchemaData file gave all five of its fields, editable.
NetworkLink
A <NetworkLink> holds a <Link><href>example.com/layer.kml</href></Link> and tells Google Earth to fetch that URL; it carries no geometry. A KML whose only content is a NetworkLink opened under KML with no layers at all, and under LIBKML as one layer, netlink, with Feature Count: 0; neither fetched anything. Our viewer behaves the same: 0 features · 0 fields, and a network trace after the drop showed requests to the map tile server and nothing else — the viewer does not fetch it. The data is at the URL, not in the file; download the target and open that.
GroundOverlay
A <GroundOverlay> drapes an image over a <LatLonBox> of <north>, <south>, <east>, <west> bounds, with <Icon><href>files/site.png</href></Icon> pointing inside the KMZ — a scanned site plan, a drone orthophoto. The KML driver ignores it (our KMZ listed only the Wells layer); LIBKML reads a POLYGON Z of the box's corners with an icon field of files/site.png — the footprint, not the image. Our viewer renders it: 1 features · 1 Point · 1 ground overlays · 1 folders, with the embedded PNG drawn over the box.
Coordinates: always lon,lat, always WGS 84
A KML has no place to declare a coordinate system, so there is no such thing as "a KML in UTM" — only a KML with the wrong numbers in it. Both drivers say so on every read: ogrinfo -al -so on any KML prints GEOGCRS["WGS 84" and ID["EPSG",4326] whatever the numbers are. Every "my KMZ is in the wrong place" is one of three mistakes.
- Swapped order. São Paulo is
<coordinates>-46.63,-23.55,0</coordinates>;ogrinfo -al sp_ok.kmlprintedPOINT Z (-46.63 -23.55 0), in Brazil. Written-23.55,-46.63,0, the same placemark printedPOINT Z (-23.55 -46.63 0)under both drivers, no warning: longitude −23.55, latitude −46.63, the South Atlantic, some 3,300 km east of Patagonia. A file in an ocean or on the wrong continent has its pairs backwards; swap them, reproject nothing. - Projected metres written as degrees. A UTM pair for the same point is roughly
333000,7395000. On read both drivers accept it silently —POINT Z (333000 7395000 0)— andogr2ogr -f GeoJSONwrote it out without a word. On write the checks fire: a GeoJSON holding that pair sent throughogr2ogr -f KMLfailed withERROR 1: Latitude 7395000.000000 is invalid. Valid range is [-90,90].; theLIBKMLwriter saidERROR 1: Invalid longitude 333000. Tell GDAL what the numbers are and let it convert:ogr2ogr -f KML out.kml in.geojson -s_srs EPSG:31983 -t_srs EPSG:4326produced<coordinates>-46.636073838011,-23.5467532379101</coordinates>. A source that carries its CRS (a Shapefile with a.prj, a GeoPackage) needs neither. A latitude past 90 behaves the same:-46.63,-93.55read silently, thenERROR 1: Latitude -93.550000 is invalidon write. - Degrees, minutes and seconds pasted in. A tuple written
46°37'48"W,23°33'0"S,0is not an error to either driver, which is the problem:KMLreturnedPOINT EMPTY;LIBKMLreturnedPOINT (46 23)— the leading integers, hemispheres dropped, a point in Saudi Arabia. Convert DMS to decimal first; our coordinate converter does it and shows the point on a map.
One more, smaller but real: a space after the comma. -46.63, -23.55, 0 is three tuples to the KML driver, which printed POINT (-46.63 0.0) — latitude gone, point on the equator — while LIBKML read it correctly.
Open or view a KMZ file online without Google Earth
Six things the viewer does with a KMZ, in the order you will meet them:
- Drop the file on the viewer. Our free KMZ viewer accepts
.kml,.kmzand GeoJSON, unzips the KMZ in the browser, and draws the placemarks with the count in the layer panel. - Folders. Listed under the file's layer entry with a show/hide toggle each; every feature carries a
folderattribute. - Click a placemark. The attributes panel shows the name, description, the WKT with a copy button, and every
ExtendedDatafield as a row —Datapairs andSchemaDataalike; the attribute table lists every feature with a search box. - Edit an attribute, set a label. Fields are editable; the
Labeldropdown picks the attribute drawn beside each feature. - Overlays and export. A
GroundOverlaywhose image is inside the KMZ is drawn over itsLatLonBox; the export menu writes GeoJSON, KML, KMZ or CSV. - Privacy. Parsing happens in your browser; the file is never sent to a server. The one thing the viewer will not do is follow a
NetworkLink.
From the terminal, ogrinfo -al -so in.kml lists layers, fields and extent. With the KML driver only, a .kmz does not open directly: ogrinfo out.kmz fails with ERROR 4: 'out.kmz' not recognized as being in a supported file format. Changing the filename to /vsizip/out.kmz may help it to be recognized. — and it does: ogrinfo -so /vsizip/out.kmz/doc.kml listed 1: props (Point). With LIBKML, ogrinfo out.kmz opens the archive as-is.
Convert KML to Shapefile, GeoJSON or GeoPackage
Every command was run against the files above; the driver is named because it decides the result.
- KML to GeoJSON:
ogr2ogr -f GeoJSON out.geojson in.kml. GeoJSON is one layer per file, so a KML with several folders half-converts: ours wroteWellsintoout.geojsonand then stopped withERROR 1: Layer 'Access roads' does not already exist in the output dataset, and cannot be created by the output driver.— a file that looks complete and is missing a folder. Name the layer as a trailing argument (ogr2ogr -f GeoJSON wells.geojson in.kml Wells, one file per folder) or convert to GeoPackage.Warning 1: Attempt to write Z geometries to layer … that does not support themis harmless. - KML or KMZ to GeoPackage:
ogr2ogr -f GPKG out.gpkg in.kmz. WithLIBKMLthe.kmzopens directly and every folder becomes a layer —1: Wells,2: Access roadsin ours. Our GeoPackage guide covers what you get on the other side. - KML to Shapefile:
ogr2ogr -f "ESRI Shapefile" out in.kml. Two things happen that are the Shapefile's fault: field names are cut at ten characters —Descriptioncame back asDescriptiowithWarning 6: Normalized/laundered field name: 'Description' to 'Descriptio', and underLIBKML, which also reads the schema fields,observation_notescame back asobservatio— and a file holds one geometry type, so theMultiGeometryplacemark failed withERROR 6: Geometry type of '3D Geometry Collection' not supported in shapefiles. Folders are fine, one Shapefile each: we gotWells.shpandAccess roads.shpin one run. Our Shapefile guide documents both rules and the encoding trap that follows them. - Shapefile or GeoPackage to KML:
ogr2ogr -f KML out.kml in.shp. TheKMLdriver converts to WGS 84 from the.prjon its own and writes the attributes asSchemaData; aDatefield drawsWarning 1: The output driver does not natively support Date type for field visit_dateand lands as the string2026/09/01. - Writing a real KMZ:
ogr2ogr -f LIBKML out.kmz in.gpkg. OnlyLIBKMLzips. TheKMLdriver takes a.kmzoutput name without complaint and writes plain XML into it —file test_write.kmzreportedXML 1.0 document, ASCII text— and our viewer refused it withError processing file. Make sure it is a valid KML or KMZ file.; renaming it.kmlfixes it.
KML vs LIBKML
KML is the old, dependency-free driver, always present; LIBKML is built on Google's libkml library, is the one you want, and is missing from many builds — ours included. When both are present GDAL prefers LIBKML for reading (the container printed using driver 'LIBKML' successful on a file KML had written).
- Attributes on read. This is the one that loses data.
KMLreads onlyNameandDescription: our file with five typedSchemaDatafields listedName: String (0.0),Description: String (0.0)and nothing else,Datapairs were dropped the same way, andogr2ogr -f GeoJSONthrough it produced properties of{"Name":"S1","Description":""}.LIBKMLread the same file assite_id: Integer,area_ha: Real,observation_notes: String,visited: Integer(Boolean),visit_date: String, plus housekeeping fields it always adds —id,Name,description,timestamp,begin,end,altitudeMode,tessellate,extrude,visibility,drawOrder,icon— andDatapairs asString. With onlyKML, every attribute you convert is silently discarded. - Attributes on write. Both write a
<Schema>plusSchemaData.KMLdeclarestype="float"and writes a boolean as1;LIBKMLdeclarestype="double",type="bool"and writestrue. Neither has a date type: both declarevisit_dateastype="string". - Folders on write.
KMLemits one<Folder>per layer;LIBKMLemits nested<Document>s and no<Folder>at all (grep -c '<Folder' lib_folders_out.kml→0).
Errors, decoded
Symptom, cause, fix; QGIS shows the same GDAL messages in its log panel.
The description is a wall of HTML
Symptom: a description reads as tags — <b>Gate locked</b> since May. <a href="example.com/page">Site record</a> — in a table or a CSV. Cause: Google Earth stores balloon content as HTML inside <description><![CDATA[…]]></description>. Both GDAL drivers return it verbatim as the description (or Description) string, and a KML written back from GeoJSON carries it escaped, <b>Gate locked</b>. Our viewer renders it: the attributes panel showed bold text and a clickable link, and the table shows the placeholder HTML in that column. Fix: nothing is broken; strip the tags after export if you need clean text, and put structured values in ExtendedData, not in the balloon.
Field names cut at ten characters
Symptom: Description becomes Descriptio on the way to Shapefile, with Warning 6: Normalized/laundered field name. Cause: the .dbf stores eleven bytes per name, one a terminator. Fix: none inside a Shapefile — rename fields to ten characters before exporting so you choose the abbreviations, or keep a GeoPackage.
Styles are gone after conversion
Symptom: a KML that was red icons and green lines in Google Earth comes back as default pushpins. Cause: none of the three formats compared here has a place for KML styles. We took a KML with two <Style>s and a <StyleMap> (grep -c '<Style' styled.kml → 3), converted to GeoJSON and back, and grep -c '<Style' roundtrip.kml printed 0 under both drivers. Fix: if the styling matters, stay in KML. ogr2ogr -f LIBKML out.kml in.kml keeps <Style> blocks and each <styleUrl>, but flattens a <StyleMap> into a plain <Style> holding its normal pair; the highlight state is gone.
A huge KMZ from Google Earth
Symptom: a KMZ of a few hundred placemarks is tens of megabytes. Cause: files/ — Google Earth embeds every photo attached to a placemark and every overlay image at full resolution. Fix: unzip -l big.kmz and read the sizes; unzip big.kmz doc.kml extracts the KML alone (ours went from three entries to one, 659 bytes) and zip stripped.kmz doc.kml rebuilds a KMZ without the images. Keep the original if it has overlays.
KML 2.1 vs 2.2 namespace
Symptom: a tool complains the file is "not KML 2.2", or a validator flags the xmlns. Cause: Google Earth 4-era files declare earth.google.com/kml/2.1 or 2.0 where the OGC standard is opengis.net/kml/2.2. GDAL does not care: the same placemark with the 2.2, 2.1 and 2.0 namespaces and with no xmlns at all printed POINT Z (-46.63 -23.55 0) under both drivers, no warning. Fix: change the xmlns value to the 2.2 one in a text editor; anything GDAL writes carries it already.
Invalid latitude, or a point off the globe
Symptom: ERROR 1: Latitude 7395000.000000 is invalid. Valid range is [-90,90]. from the KML writer, ERROR 1: Invalid longitude 333000 or ERROR 1: Invalid latitude -93.55 from LIBKML, and a conversion that stops with Terminating translation prematurely. Cause: metres, or degrees past their range, where WGS 84 degrees belong. Fix: never edit the numbers; tell GDAL the source system with -s_srs and -t_srs EPSG:4326 as in the coordinates section. Swapped pairs are the other half of this symptom.
KML vs GeoJSON vs Shapefile vs GeoPackage
No table — pick the sentence that fits.
KML is the right answer when the destination is Google Earth, a field phone, or a client who will double-click the file and expect it to look the same as on your screen. It is the only one of the four that carries its own styling, icons and overlay images. Keep the attributes in ExtendedData, and a copy in something typed.
KML is the wrong answer when the attributes matter — dates and booleans arrive as strings, and a GDAL build without LIBKML drops every attribute on read; when the data is big — verbose XML, no index; or when it must stay in a projected system.
GeoJSON is the right answer when a browser or an API will read it: typed attributes, no styling, one layer per file, WGS 84 like KML.
Shapefile is the right answer when the other side accepts nothing else; our how-to on opening a Shapefile online covers what to do when one arrives.
GeoPackage is the right answer when the data is yours to keep: every folder a layer, real field types, any CRS, one file. It is what a KMZ should become on arrival.
For teams
We build Geodocs, a platform for field data and GIS teams, and a Google Earth export from a site visit is a common upload. The platform reads the placemarks and their ExtendedData, keeps the folders as layers, and puts the result on a team map, so the KMZ becomes the input to the review and the report rather than the thing everyone is emailing around.
Spotted an error, or a gotcha we missed? Tell us; we re-run this page rather than copy-pasting it.
Last verified: 10 September 2026, GDAL 3.13.3 locally and GDAL 3.14.0dev in the OSGeo Docker image.