Task 342: .KMZ File Format
Task 342: .KMZ File Format
KMZ File Format Specifications
The .KMZ file format is a compressed archive format based on the ZIP file format, specifically designed for packaging Keyhole Markup Language (KML) files along with optional supporting resources like images, models, or other data. It is defined by the Open Geospatial Consortium (OGC) as part of the KML specification (Annex C). KMZ files use standard ZIP compression (typically no compression or DEFLATE, compatible with PKWARE ZIP version 2.0 or later) to bundle a primary KML file (often named "doc.kml" as the root document, though the first .kml file in the archive is used if not specified) and any referenced assets. The format supports dynamic remote links but focuses on efficient local storage and transfer, offering roughly 10:1 compression ratios for typical KML data. Metadata support is limited, with a fixed coordinate reference system (WGS84 for longitude/latitude in decimal degrees, EGM96 Geoid for altitude in meters). KMZ files are cross-platform and widely used in GIS applications like Google Earth.
List of Properties Intrinsic to the .KMZ File Format
These properties are derived from the underlying ZIP structure, as KMZ is essentially a ZIP archive with specific content constraints. They include archive-level metadata and per-entry (file) metadata, which are stored in binary headers within the file. KMZ-specific properties (e.g., root KML identification) are inferred from the contents but are not separate binary fields. Properties do not include the actual compressed data or external OS-dependent attributes like permissions (which vary by file system).
Archive-Level Properties:
- ZIP file comment (variable-length string, often empty in KMZ files).
- Number of central directory entries (total number of files in the archive, 16-bit integer).
- Total number of entries in the archive (16-bit integer; matches the above in single-disk archives).
- Size of the central directory (32-bit integer, in bytes).
- Offset of the start of the central directory (32-bit integer, from the beginning of the file).
- Disk number (16-bit integer; usually 0 for non-spanned archives).
- Disk number where central directory starts (16-bit integer; usually 0).
- ZIP64 extensions (if present: 64-bit versions of sizes, offsets, and entry counts for large files >4GB).
Per-Entry (File) Properties:
- File name (variable-length UTF-8 string).
- Version made by (16-bit integer, e.g., 20 for ZIP 2.0).
- Version needed to extract (16-bit integer, minimum required ZIP version).
- General purpose bit flag (16-bit integer, bits indicating encryption, data descriptor usage, UTF-8 naming, etc.).
- Compression method (16-bit integer, e.g., 0 for stored/no compression, 8 for DEFLATE).
- Last modification time (16-bit integer, in DOS format: bits 0-4=second/2, 5-10=minute, 11-15=hour).
- Last modification date (16-bit integer, in DOS format: bits 0-4=day, 5-8=month, 9-15=year-1980).
- CRC-32 checksum of uncompressed data (32-bit integer).
- Compressed size (32-bit integer; or ZIP64 64-bit if >4GB).
- Uncompressed size (32-bit integer; or ZIP64 64-bit if >4GB).
- File comment (variable-length string, often empty).
- Disk number start (16-bit integer; usually 0).
- Internal file attributes (16-bit integer, e.g., bit 0 for text/binary).
- External file attributes (32-bit integer, OS-specific, e.g., Unix permissions or DOS read-only).
- Relative offset of local file header (32-bit integer; or ZIP64 64-bit).
- Extra field (variable-length, optional extensions like timestamps or ZIP64 sizes).
KMZ-Specific Inferred Properties (Derived from Entries):
- Root KML file name (string: typically "doc.kml", or the first entry ending in ".kml").
- List of supporting files (array of file names excluding the root KML, e.g., images or models referenced in KML).
Two Direct Download Links for .KMZ Files
- https://example-files.online-convert.com/misc/kmz/example.kmz (a simple example KMZ file for testing).
- https://gis.ncdc.noaa.gov/kml/isd.kmz (a KMZ file from NOAA's Integrated Surface Dataset).
Ghost Blog Embedded HTML/JavaScript for Drag-and-Drop .KMZ Property Dump
This is a self-contained HTML page with embedded JavaScript that allows dragging and dropping a .KMZ file. It parses the file as a ZIP archive (without decompressing data) and dumps the properties to the screen. Uses DataView for binary parsing. Assumes no ZIP64 for simplicity; errors if signatures not found.
Python Class for .KMZ Handling
This class uses Python's built-in struct
for binary decoding and io
for handling. It can read/decode properties from a .KMZ file, print them to console, and write a simple new KMZ (with one uncompressed KML file for demonstration). Assumes no ZIP64.
import struct
import io
import datetime
class KMZParser:
def __init__(self, filename=None):
self.properties = None
if filename:
self.load(filename)
def load(self, filename):
with open(filename, 'rb') as f:
data = f.read()
self.data = data
self.properties = self._parse()
return self.properties
def _find_eocd(self):
for i in range(len(self.data) - 22, -1, -1):
if struct.unpack_from('<I', self.data, i)[0] == 0x06054B50:
return i
raise ValueError("EOCD signature not found")
def _parse(self):
eocd_offset = self._find_eocd()
archive = struct.unpack_from('<HHHHIIH', self.data, eocd_offset + 4)
props = {
'archive': {
'diskNum': archive[0],
'cdDiskNum': archive[1],
'entriesThisDisk': archive[2],
'totalEntries': archive[3],
'cdSize': archive[4],
'cdOffset': archive[5],
'commentLen': archive[6],
'comment': self.data[eocd_offset + 22 : eocd_offset + 22 + archive[6]].decode('utf-8', errors='ignore')
},
'entries': []
}
offset = props['archive']['cdOffset']
for _ in range(props['archive']['totalEntries']):
sig = struct.unpack_from('<I', self.data, offset)[0]
if sig != 0x02014B50:
raise ValueError("CD signature not found")
entry_data = struct.unpack_from('<HHHHHHHHIIIHH', self.data, offset + 4)
entry = {
'versionMade': entry_data[0],
'versionNeeded': entry_data[1],
'bitFlag': entry_data[2],
'compMethod': entry_data[3],
'modTime': entry_data[4],
'modDate': entry_data[5],
'crc32': entry_data[6],
'compSize': entry_data[7],
'uncompSize': entry_data[8],
'nameLen': entry_data[9],
'extraLen': entry_data[10],
'commentLen': entry_data[11],
'diskStart': entry_data[12],
'intAttr': entry_data[13],
'extAttr': entry_data[14],
'localOffset': entry_data[15]
}
base = offset + 46
entry['name'] = self.data[base : base + entry['nameLen']].decode('utf-8')
base += entry['nameLen']
entry['extra'] = self.data[base : base + entry['extraLen']]
base += entry['extraLen']
entry['comment'] = self.data[base : base + entry['commentLen']].decode('utf-8', errors='ignore')
props['entries'].append(entry)
offset += 46 + entry['nameLen'] + entry['extraLen'] + entry['commentLen']
# KMZ-specific
props['kmzRoot'] = next((e['name'] for e in props['entries'] if e['name'].endswith('.kml')), 'doc.kml')
props['kmzSupport'] = [e['name'] for e in props['entries'] if e['name'] != props['kmzRoot']]
return props
def print_properties(self):
if not self.properties:
print("No properties loaded")
return
print("Archive Properties:")
for k, v in self.properties['archive'].items():
print(f" {k}: {v}")
print("\nEntries:")
for i, e in enumerate(self.properties['entries'], 1):
print(f"Entry {i}:")
for k, v in e.items():
print(f" {k}: {v}")
print("\nKMZ-Specific:")
print(f" Root KML: {self.properties['kmzRoot']}")
print(f" Supporting Files: {self.properties['kmzSupport']}")
def write_simple_kmz(self, output_filename, kml_content=b'<kml></kml>', root_name='doc.kml'):
# Simple write: one uncompressed file, no extra/comment
local_header = struct.pack('<IHHHHIIIIIHH', 0x04034B50, 10, 0, 0, 0, 0, 0, len(kml_content), len(kml_content), len(root_name), 0, 0)
local_header += root_name.encode('utf-8')
cd_header = struct.pack('<IHHHHHHIIIHHHHII', 0x02014B50, 10, 10, 0, 0, 0, 0, 0, len(kml_content), len(kml_content), len(root_name), 0, 0, 0, 0, 0, 0)
cd_header += root_name.encode('utf-8')
eocd = struct.pack('<IHHHHIIH', 0x06054B50, 0, 0, 1, 1, len(cd_header), len(local_header) + len(kml_content), 0)
with open(output_filename, 'wb') as f:
f.write(local_header)
f.write(kml_content)
f.write(cd_header)
f.write(eocd)
# Example usage:
# parser = KMZParser('example.kmz')
# parser.print_properties()
# parser.write_simple_kmz('new.kmz')
Java Class for .KMZ Handling
This class uses ByteBuffer
for decoding. It can read/decode properties, print to console, and write a simple new KMZ. Assumes little-endian and no ZIP64.
import java.io.*;
import java.nio.*;
import java.nio.channels.FileChannel;
import java.nio.file.*;
public class KMZParser {
private ByteBuffer buffer;
private String archiveProps;
private String entriesProps;
private String kmzProps;
public KMZParser(String filename) throws IOException {
load(filename);
}
public void load(String filename) throws IOException {
byte[] data = Files.readAllBytes(Paths.get(filename));
buffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
parse();
}
private int findEOCD() {
for (int i = buffer.capacity() - 22; i >= 0; i--) {
if (buffer.getInt(i) == 0x06054B50) return i;
}
throw new RuntimeException("EOCD signature not found");
}
private void parse() {
int eocdOffset = findEOCD();
buffer.position(eocdOffset + 4);
short diskNum = buffer.getShort();
short cdDiskNum = buffer.getShort();
short entriesThisDisk = buffer.getShort();
short totalEntries = buffer.getShort();
int cdSize = buffer.getInt();
int cdOffset = buffer.getInt();
short commentLen = buffer.getShort();
byte[] commentBytes = new byte[commentLen];
buffer.get(commentBytes);
String comment = new String(commentBytes);
StringBuilder sb = new StringBuilder();
sb.append("Archive Properties:\n")
.append(" diskNum: ").append(diskNum).append("\n")
.append(" cdDiskNum: ").append(cdDiskNum).append("\n")
.append(" entriesThisDisk: ").append(entriesThisDisk).append("\n")
.append(" totalEntries: ").append(totalEntries).append("\n")
.append(" cdSize: ").append(cdSize).append("\n")
.append(" cdOffset: ").append(cdOffset).append("\n")
.append(" comment: ").append(comment).append("\n");
archiveProps = sb.toString();
sb = new StringBuilder("\nEntries:\n");
String rootKml = "doc.kml";
StringBuilder support = new StringBuilder();
buffer.position(cdOffset);
for (int i = 0; i < totalEntries; i++) {
if (buffer.getInt() != 0x02014B50) throw new RuntimeException("CD signature not found");
short versionMade = buffer.getShort();
short versionNeeded = buffer.getShort();
short bitFlag = buffer.getShort();
short compMethod = buffer.getShort();
short modTime = buffer.getShort();
short modDate = buffer.getShort();
int crc32 = buffer.getInt();
int compSize = buffer.getInt();
int uncompSize = buffer.getInt();
short nameLen = buffer.getShort();
short extraLen = buffer.getShort();
short commentLen = buffer.getShort();
short diskStart = buffer.getShort();
short intAttr = buffer.getShort();
int extAttr = buffer.getInt();
int localOffset = buffer.getInt();
byte[] nameBytes = new byte[nameLen];
buffer.get(nameBytes);
String name = new String(nameBytes);
byte[] extra = new byte[extraLen];
buffer.get(extra);
byte[] entryCommentBytes = new byte[commentLen];
buffer.get(entryCommentBytes);
String entryComment = new String(entryCommentBytes);
sb.append("Entry ").append(i + 1).append(":\n")
.append(" name: ").append(name).append("\n")
.append(" versionMade: ").append(versionMade).append("\n")
.append(" versionNeeded: ").append(versionNeeded).append("\n")
.append(" bitFlag: ").append(bitFlag).append("\n")
.append(" compMethod: ").append(compMethod).append("\n")
.append(" modTime: ").append(modTime).append("\n")
.append(" modDate: ").append(modDate).append("\n")
.append(" crc32: ").append(crc32).append("\n")
.append(" compSize: ").append(compSize).append("\n")
.append(" uncompSize: ").append(uncompSize).append("\n")
.append(" comment: ").append(entryComment).append("\n")
.append(" diskStart: ").append(diskStart).append("\n")
.append(" intAttr: ").append(intAttr).append("\n")
.append(" extAttr: ").append(extAttr).append("\n")
.append(" localOffset: ").append(localOffset).append("\n");
if (name.endsWith(".kml") && rootKml.equals("doc.kml")) rootKml = name;
else support.append(name).append(", ");
}
entriesProps = sb.toString();
kmzProps = "\nKMZ-Specific:\n Root KML: " + rootKml + "\n Supporting Files: " + (support.length() > 0 ? support.substring(0, support.length() - 2) : "[]");
}
public void printProperties() {
System.out.print(archiveProps + entriesProps + kmzProps);
}
public void writeSimpleKmz(String outputFilename, byte[] kmlContent, String rootName) throws IOException {
// Simple uncompressed KMZ with one file
ByteBuffer local = ByteBuffer.allocate(30 + rootName.length()).order(ByteOrder.LITTLE_ENDIAN);
local.putInt(0x04034B50);
local.putShort((short)10); // version needed
local.putShort((short)0); // bit flag
local.putShort((short)0); // comp method (stored)
local.putShort((short)0); // time
local.putShort((short)0); // date
local.putInt(0); // crc (dummy)
local.putInt(kmlContent.length); // comp size
local.putInt(kmlContent.length); // uncomp size
local.putShort((short)rootName.length());
local.putShort((short)0); // extra len
local.put(rootName.getBytes());
ByteBuffer cd = ByteBuffer.allocate(46 + rootName.length()).order(ByteOrder.LITTLE_ENDIAN);
cd.putInt(0x02014B50);
cd.putShort((short)10); // version made
cd.putShort((short)10); // version needed
cd.putShort((short)0); // bit flag
cd.putShort((short)0); // comp method
cd.putShort((short)0); // time
cd.putShort((short)0); // date
cd.putInt(0); // crc
cd.putInt(kmlContent.length);
cd.putInt(kmlContent.length);
cd.putShort((short)rootName.length());
cd.putShort((short)0); // extra
cd.putShort((short)0); // comment
cd.putShort((short)0); // disk start
cd.putShort((short)0); // int attr
cd.putInt(0); // ext attr
cd.putInt(0); // local offset
cd.put(rootName.getBytes());
ByteBuffer eocd = ByteBuffer.allocate(22).order(ByteOrder.LITTLE_ENDIAN);
eocd.putInt(0x06054B50);
eocd.putShort((short)0); // disk num
eocd.putShort((short)0); // cd disk
eocd.putShort((short)1); // entries this disk
eocd.putShort((short)1); // total entries
eocd.putInt(cd.capacity()); // cd size
eocd.putInt(local.capacity() + kmlContent.length); // cd offset
eocd.putShort((short)0); // comment len
try (FileOutputStream fos = new FileOutputStream(outputFilename)) {
fos.write(local.array());
fos.write(kmlContent);
fos.write(cd.array());
fos.write(eocd.array());
}
}
// Example usage:
// public static void main(String[] args) throws IOException {
// KMZParser parser = new KMZParser("example.kmz");
// parser.printProperties();
// parser.writeSimpleKmz("new.kmz", "<kml></kml>".getBytes(), "doc.kml");
// }
}
JavaScript Class for .KMZ Handling
This class is for Node.js (uses fs
), parses .KMZ, prints properties to console, and can write a simple KMZ. Assumes no ZIP64.
const fs = require('fs');
class KMZParser {
constructor(filename) {
if (filename) this.load(filename);
}
load(filename) {
this.data = fs.readFileSync(filename);
this.view = new DataView(this.data.buffer);
this.properties = this.parse();
return this.properties;
}
findEOCD() {
for (let i = this.data.length - 22; i >= 0; i--) {
if (this.view.getUint32(i, true) === 0x06054B50) return i;
}
throw new Error("EOCD signature not found");
}
parse() {
const eocdOffset = this.findEOCD();
this.view.setPosition = offset => {}; // DataView no position, use per call
const archive = {
diskNum: this.view.getUint16(eocdOffset + 4, true),
cdDiskNum: this.view.getUint16(eocdOffset + 6, true),
entriesThisDisk: this.view.getUint16(eocdOffset + 8, true),
totalEntries: this.view.getUint16(eocdOffset + 10, true),
cdSize: this.view.getUint32(eocdOffset + 12, true),
cdOffset: this.view.getUint32(eocdOffset + 16, true),
commentLen: this.view.getUint16(eocdOffset + 20, true),
comment: new TextDecoder().decode(this.data.subarray(eocdOffset + 22, eocdOffset + 22 + this.view.getUint16(eocdOffset + 20, true)))
};
const entries = [];
let offset = archive.cdOffset;
for (let i = 0; i < archive.totalEntries; i++) {
if (this.view.getUint32(offset, true) !== 0x02014B50) throw new Error("CD signature not found");
const entry = {
versionMade: this.view.getUint16(offset + 4, true),
versionNeeded: this.view.getUint16(offset + 6, true),
bitFlag: this.view.getUint16(offset + 8, true),
compMethod: this.view.getUint16(offset + 10, true),
modTime: this.view.getUint16(offset + 12, true),
modDate: this.view.getUint16(offset + 14, true),
crc32: this.view.getUint32(offset + 16, true),
compSize: this.view.getUint32(offset + 20, true),
uncompSize: this.view.getUint32(offset + 24, true),
nameLen: this.view.getUint16(offset + 28, true),
extraLen: this.view.getUint16(offset + 30, true),
commentLen: this.view.getUint16(offset + 32, true),
diskStart: this.view.getUint16(offset + 34, true),
intAttr: this.view.getUint16(offset + 36, true),
extAttr: this.view.getUint32(offset + 38, true),
localOffset: this.view.getUint32(offset + 42, true)
};
entry.name = new TextDecoder().decode(this.data.subarray(offset + 46, offset + 46 + entry.nameLen));
entry.extra = this.data.subarray(offset + 46 + entry.nameLen, offset + 46 + entry.nameLen + entry.extraLen);
entry.comment = new TextDecoder().decode(this.data.subarray(offset + 46 + entry.nameLen + entry.extraLen, offset + 46 + entry.nameLen + entry.extraLen + entry.commentLen));
entries.push(entry);
offset += 46 + entry.nameLen + entry.extraLen + entry.commentLen;
}
const kmzRoot = entries.find(e => e.name.endsWith('.kml'))?.name || 'doc.kml';
const kmzSupport = entries.filter(e => e.name !== kmzRoot).map(e => e.name);
return { archive, entries, kmzRoot, kmzSupport };
}
printProperties() {
console.log('Archive Properties:');
console.log(this.properties.archive);
console.log('\nEntries:');
this.properties.entries.forEach((e, i) => {
console.log(`Entry ${i + 1}:`);
console.log(e);
});
console.log('\nKMZ-Specific:');
console.log('Root KML:', this.properties.kmzRoot);
console.log('Supporting Files:', this.properties.kmzSupport);
}
writeSimpleKmz(outputFilename, kmlContent, rootName = 'doc.kml') {
const kmlBuf = Buffer.from(kmlContent);
const localHeader = Buffer.alloc(30 + rootName.length);
const view = new DataView(localHeader.buffer);
view.setUint32(0, 0x04034B50, true);
view.setUint16(4, 10, true); // version needed
view.setUint16(6, 0, true); // bit flag
view.setUint16(8, 0, true); // comp method
view.setUint16(10, 0, true); // time
view.setUint16(12, 0, true); // date
view.setUint32(14, 0, true); // crc
view.setUint32(18, kmlBuf.length, true); // comp size
view.setUint32(22, kmlBuf.length, true); // uncomp size
view.setUint16(26, rootName.length, true);
view.setUint16(28, 0, true); // extra len
localHeader.write(rootName, 30);
const cdHeader = Buffer.alloc(46 + rootName.length);
const cdView = new DataView(cdHeader.buffer);
cdView.setUint32(0, 0x02014B50, true);
cdView.setUint16(4, 10, true); // version made
cdView.setUint16(6, 10, true); // version needed
cdView.setUint16(8, 0, true);
cdView.setUint16(10, 0, true);
cdView.setUint16(12, 0, true);
cdView.setUint16(14, 0, true);
cdView.setUint32(16, 0, true);
cdView.setUint32(20, kmlBuf.length, true);
cdView.setUint32(24, kmlBuf.length, true);
cdView.setUint16(28, rootName.length, true);
cdView.setUint16(30, 0, true);
cdView.setUint16(32, 0, true);
cdView.setUint16(34, 0, true);
cdView.setUint16(36, 0, true);
cdView.setUint32(38, 0, true);
cdView.setUint32(42, 0, true);
cdHeader.write(rootName, 46);
const eocd = Buffer.alloc(22);
const eocdView = new DataView(eocd.buffer);
eocdView.setUint32(0, 0x06054B50, true);
eocdView.setUint16(4, 0, true);
eocdView.setUint16(6, 0, true);
eocdView.setUint16(8, 1, true);
eocdView.setUint16(10, 1, true);
eocdView.setUint32(12, cdHeader.length, true);
eocdView.setUint32(16, localHeader.length + kmlBuf.length, true);
eocdView.setUint16(20, 0, true);
fs.writeFileSync(outputFilename, Buffer.concat([localHeader, kmlBuf, cdHeader, eocd]));
}
}
// Example usage:
// const parser = new KMZParser('example.kmz');
// parser.printProperties();
// parser.writeSimpleKmz('new.kmz', '<kml></kml>');
C Class for .KMZ Handling
This is a C++ class (as "class" implies OO; pure C would use structs/functions). Uses fstream
for I/O, parses .KMZ, prints to console, and can write a simple KMZ. Assumes little-endian host and no ZIP64.
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <cstdint>
#include <cstring>
class KMZParser {
private:
std::vector<uint8_t> data;
struct ArchiveProps {
uint16_t diskNum, cdDiskNum, entriesThisDisk, totalEntries;
uint32_t cdSize, cdOffset;
std::string comment;
};
struct Entry {
uint16_t versionMade, versionNeeded, bitFlag, compMethod, modTime, modDate;
uint32_t crc32, compSize, uncompSize;
std::string name, comment;
uint16_t diskStart, intAttr;
uint32_t extAttr, localOffset;
std::vector<uint8_t> extra;
};
ArchiveProps archive;
std::vector<Entry> entries;
std::string kmzRoot;
std::vector<std::string> kmzSupport;
public:
KMZParser(const std::string& filename = "") {
if (!filename.empty()) load(filename);
}
void load(const std::string& filename) {
std::ifstream file(filename, std::ios::binary | std::ios::ate);
if (!file) throw std::runtime_error("Cannot open file");
size_t size = file.tellg();
data.resize(size);
file.seekg(0);
file.read(reinterpret_cast<char*>(data.data()), size);
parse();
}
size_t findEOCD() {
for (size_t i = data.size() - 22; i != static_cast<size_t>(-1); --i) {
uint32_t sig;
std::memcpy(&sig, &data[i], 4);
if (sig == 0x06054B50) return i;
}
throw std::runtime_error("EOCD signature not found");
}
void parse() {
size_t eocdOffset = findEOCD();
std::memcpy(&archive.diskNum, &data[eocdOffset + 4], 2);
std::memcpy(&archive.cdDiskNum, &data[eocdOffset + 6], 2);
std::memcpy(&archive.entriesThisDisk, &data[eocdOffset + 8], 2);
std::memcpy(&archive.totalEntries, &data[eocdOffset + 10], 2);
std::memcpy(&archive.cdSize, &data[eocdOffset + 12], 4);
std::memcpy(&archive.cdOffset, &data[eocdOffset + 16], 4);
uint16_t commentLen;
std::memcpy(&commentLen, &data[eocdOffset + 20], 2);
archive.comment = std::string(reinterpret_cast<char*>(&data[eocdOffset + 22]), commentLen);
size_t offset = archive.cdOffset;
entries.resize(archive.totalEntries);
for (size_t i = 0; i < archive.totalEntries; ++i) {
uint32_t sig;
std::memcpy(&sig, &data[offset], 4);
if (sig != 0x02014B50) throw std::runtime_error("CD signature not found");
std::memcpy(&entries[i].versionMade, &data[offset + 4], 2);
std::memcpy(&entries[i].versionNeeded, &data[offset + 6], 2);
std::memcpy(&entries[i].bitFlag, &data[offset + 8], 2);
std::memcpy(&entries[i].compMethod, &data[offset + 10], 2);
std::memcpy(&entries[i].modTime, &data[offset + 12], 2);
std::memcpy(&entries[i].modDate, &data[offset + 14], 2);
std::memcpy(&entries[i].crc32, &data[offset + 16], 4);
std::memcpy(&entries[i].compSize, &data[offset + 20], 4);
std::memcpy(&entries[i].uncompSize, &data[offset + 24], 4);
uint16_t nameLen, extraLen, commentLen;
std::memcpy(&nameLen, &data[offset + 28], 2);
std::memcpy(&extraLen, &data[offset + 30], 2);
std::memcpy(&commentLen, &data[offset + 32], 2);
std::memcpy(&entries[i].diskStart, &data[offset + 34], 2);
std::memcpy(&entries[i].intAttr, &data[offset + 36], 2);
std::memcpy(&entries[i].extAttr, &data[offset + 38], 4);
std::memcpy(&entries[i].localOffset, &data[offset + 42], 4);
entries[i].name = std::string(reinterpret_cast<char*>(&data[offset + 46]), nameLen);
entries[i].extra.assign(&data[offset + 46 + nameLen], &data[offset + 46 + nameLen + extraLen]);
entries[i].comment = std::string(reinterpret_cast<char*>(&data[offset + 46 + nameLen + extraLen]), commentLen);
offset += 46 + nameLen + extraLen + commentLen;
}
kmzRoot = "doc.kml";
for (const auto& e : entries) {
if (e.name.find(".kml") != std::string::npos) {
kmzRoot = e.name;
break;
}
}
kmzSupport.clear();
for (const auto& e : entries) {
if (e.name != kmzRoot) kmzSupport.push_back(e.name);
}
}
void printProperties() const {
std::cout << "Archive Properties:" << std::endl;
std::cout << " diskNum: " << archive.diskNum << std::endl;
std::cout << " cdDiskNum: " << archive.cdDiskNum << std::endl;
std::cout << " entriesThisDisk: " << archive.entriesThisDisk << std::endl;
std::cout << " totalEntries: " << archive.totalEntries << std::endl;
std::cout << " cdSize: " << archive.cdSize << std::endl;
std::cout << " cdOffset: " << archive.cdOffset << std::endl;
std::cout << " comment: " << archive.comment << std::endl;
std::cout << "\nEntries:" << std::endl;
for (size_t i = 0; i < entries.size(); ++i) {
std::cout << "Entry " << i + 1 << ":" << std::endl;
std::cout << " name: " << entries[i].name << std::endl;
std::cout << " versionMade: " << entries[i].versionMade << std::endl;
std::cout << " versionNeeded: " << entries[i].versionNeeded << std::endl;
std::cout << " bitFlag: " << entries[i].bitFlag << std::endl;
std::cout << " compMethod: " << entries[i].compMethod << std::endl;
std::cout << " modTime: " << entries[i].modTime << std::endl;
std::cout << " modDate: " << entries[i].modDate << std::endl;
std::cout << " crc32: " << entries[i].crc32 << std::endl;
std::cout << " compSize: " << entries[i].compSize << std::endl;
std::cout << " uncompSize: " << entries[i].uncompSize << std::endl;
std::cout << " comment: " << entries[i].comment << std::endl;
std::cout << " diskStart: " << entries[i].diskStart << std::endl;
std::cout << " intAttr: " << entries[i].intAttr << std::endl;
std::cout << " extAttr: " << entries[i].extAttr << std::endl;
std::cout << " localOffset: " << entries[i].localOffset << std::endl;
}
std::cout << "\nKMZ-Specific:" << std::endl;
std::cout << " Root KML: " << kmzRoot << std::endl;
std::cout << " Supporting Files: ";
for (const auto& s : kmzSupport) std::cout << s << " ";
std::cout << std::endl;
}
void writeSimpleKmz(const std::string& outputFilename, const std::vector<uint8_t>& kmlContent, const std::string& rootName = "doc.kml") {
std::ofstream file(outputFilename, std::ios::binary);
if (!file) throw std::runtime_error("Cannot write file");
// Local header
uint32_t sig = 0x04034B50;
uint16_t version = 10, flag = 0, method = 0, time = 0, date = 0;
uint32_t crc = 0, compSize = kmlContent.size(), uncompSize = kmlContent.size();
uint16_t nameLen = rootName.length(), extraLen = 0;
file.write(reinterpret_cast<const char*>(&sig), 4);
file.write(reinterpret_cast<const char*>(&version), 2);
file.write(reinterpret_cast<const char*>(&flag), 2);
file.write(reinterpret_cast<const char*>(&method), 2);
file.write(reinterpret_cast<const char*>(&time), 2);
file.write(reinterpret_cast<const char*>(&date), 2);
file.write(reinterpret_cast<const char*>(&crc), 4);
file.write(reinterpret_cast<const char*>(&compSize), 4);
file.write(reinterpret_cast<const char*>(&uncompSize), 4);
file.write(reinterpret_cast<const char*>(&nameLen), 2);
file.write(reinterpret_cast<const char*>(&extraLen), 2);
file << rootName;
file.write(reinterpret_cast<const char*>(kmlContent.data()), kmlContent.size());
// CD header
sig = 0x02014B50;
uint16_t versionMade = 10;
uint16_t commentLen = 0, diskStart = 0, intAttr = 0;
uint32_t extAttr = 0, localOffset = 0;
file.write(reinterpret_cast<const char*>(&sig), 4);
file.write(reinterpret_cast<const char*>(&versionMade), 2);
file.write(reinterpret_cast<const char*>(&version), 2);
file.write(reinterpret_cast<const char*>(&flag), 2);
file.write(reinterpret_cast<const char*>(&method), 2);
file.write(reinterpret_cast<const char*>(&time), 2);
file.write(reinterpret_cast<const char*>(&date), 2);
file.write(reinterpret_cast<const char*>(&crc), 4);
file.write(reinterpret_cast<const char*>(&compSize), 4);
file.write(reinterpret_cast<const char*>(&uncompSize), 4);
file.write(reinterpret_cast<const char*>(&nameLen), 2);
file.write(reinterpret_cast<const char*>(&extraLen), 2);
file.write(reinterpret_cast<const char*>(&commentLen), 2);
file.write(reinterpret_cast<const char*>(&diskStart), 2);
file.write(reinterpret_cast<const char*>(&intAttr), 2);
file.write(reinterpret_cast<const char*>(&extAttr), 4);
file.write(reinterpret_cast<const char*>(&localOffset), 4);
file << rootName;
// EOCD
sig = 0x06054B50;
uint16_t diskNum = 0, cdDisk = 0, entriesDisk = 1, totalEntries = 1;
uint32_t cdSize = 46 + nameLen, cdOffset = 30 + nameLen + kmlContent.size();
uint16_t eCommentLen = 0;
file.write(reinterpret_cast<const char*>(&sig), 4);
file.write(reinterpret_cast<const char*>(&diskNum), 2);
file.write(reinterpret_cast<const char*>(&cdDisk), 2);
file.write(reinterpret_cast<const char*>(&entriesDisk), 2);
file.write(reinterpret_cast<const char*>(&totalEntries), 2);
file.write(reinterpret_cast<const char*>(&cdSize), 4);
file.write(reinterpret_cast<const char*>(&cdOffset), 4);
file.write(reinterpret_cast<const char*>(&eCommentLen), 2);
}
};
// Example usage:
// int main() {
// try {
// KMZParser parser("example.kmz");
// parser.printProperties();
// std::vector<uint8_t> kml = {'<', 'k', 'm', 'l', '>', '<', '/', 'k', 'm', 'l', '>'};
// parser.writeSimpleKmz("new.kmz", kml);
// } catch (const std::exception& e) {
// std::cerr << e.what() << std::endl;
// }
// return 0;
// }