| name | kml-generator |
| description | Generate KML files for visualizing building locations in Google Earth. Use when the user asks to "create a KML file", "generate KML", "make a map for Google Earth", "visualize addresses", "plot buildings on a map", "export portfolio locations", or mentions creating Google Earth visualizations from addresses or building data.
|
| version | 1.0.0 |
KML Generator
Generate KML (Keyhole Markup Language) files for visualizing building locations in Google Earth.
No MCP dependencies - Uses public Nominatim API for geocoding
When to Use
- Converting address lists to Google Earth placemarks
- Generating KML files from building/property data
- Creating portfolio location visualizations
- Exporting geocoded data for presentations
- Preparing spatial data for Google Earth
Workflow
Step 1: Gather Input Data
Accept input in one of these formats:
Address list:
123 Main St, New York, NY 10001
456 Park Ave, Boston, MA 02108
789 State St, Chicago, IL 60601
CSV with addresses:
address,name,description
123 Main St New York NY,Building A,Office Tower
456 Park Ave Boston MA,Building B,Retail Center
CSV with coordinates:
lat,lng,name,description
40.7128,-74.0060,Building A,Office Tower
42.3601,-71.0589,Building B,Retail Center
JSON from Audette:
[
{
"name": "Building A",
"address": "123 Main St, New York, NY",
"lat": 40.7128,
"lng": -74.0060,
"metadata": {
"gfa": 50000,
"carbon_intensity": 3.2
}
}
]
Step 2: Geocode Addresses (if needed)
If input includes addresses without coordinates, use Nominatim API:
curl "https://nominatim.openstreetmap.org/search?format=json&q=${address}" \
-H "User-Agent: audette-skills/1.0 (https://github.com/soapboxbuild/audette-skills)"
Example:
curl "https://nominatim.openstreetmap.org/search?format=json&q=123+Main+St+New+York+NY" \
-H "User-Agent: Audette-Orchestrator/1.0"
Response:
[
{
"lat": "40.7128",
"lon": "-74.0060",
"display_name": "123 Main Street, New York, NY, USA",
"type": "building"
}
]
Rate limit: 1 request per second. Add 1-second delay between calls.
Batch processing:
import time
import requests
geocoded = []
for address in addresses:
response = requests.get(
"https://nominatim.openstreetmap.org/search",
params={"format": "json", "q": address},
headers={"User-Agent": "Audette-Orchestrator/1.0"}
)
if response.ok and response.json():
result = response.json()[0]
geocoded.append({
"address": address,
"lat": float(result["lat"]),
"lng": float(result["lon"])
})
time.sleep(1)
Error handling:
- If geocoding fails for an address, ask user to provide coordinates manually
- If multiple results returned, take first result or ask user to confirm
Step 3: Generate KML File
Create KML XML structure:
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<Document>
<name>{{document_name}}</name>
<description>{{document_description}}</description>
<Style id="default">
<IconStyle>
<Icon>
<href>http://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png</href>
</Icon>
</IconStyle>
</Style>
{{#each placemarks}}
<Placemark>
<name>{{name}}</name>
<description><![CDATA[{{description}}]]></description>
<styleUrl>#default</styleUrl>
<Point>
<coordinates>{{lng}},{{lat}},0</coordinates>
</>
{{#if metadata}}
{{#each metadata}}
{{this}}
{{/each}}
{{/if}}
{{/each}}
Python script for generation:
def generate_kml(placemarks, document_name="Portfolio Locations", description=""):
kml_lines = [
'<?xml version="1.0" encoding="UTF-8"?>',
'<kml xmlns="http://www.opengis.net/kml/2.2">',
' <Document>',
f' <name>{document_name}</name>',
f' <description>{description}</description>',
'',
' <Style id="default">',
' <IconStyle>',
' <Icon>',
' <href>http://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png</href>',
' </Icon>',
' </IconStyle>',
' </Style>',
''
]
for pm in placemarks:
kml_lines.extend([
' <Placemark>',
f' <name>{escape_xml(pm["name"])}</name>',
f' <description><![CDATA[{pm.get("description", "")}]]></description>',
' <styleUrl>#default</styleUrl>',
' <Point>',
f' <coordinates>{pm["lng"]},{pm["lat"]},0</coordinates>',
' </Point>',
])
if "metadata" in pm and pm["metadata"]:
kml_lines.append()
key, value pm[].items():
kml_lines.extend([
,
,
])
kml_lines.append()
kml_lines.append()
kml_lines.append()
kml_lines.extend([
,
])
.join(kml_lines)
():
(text
.replace(, )
.replace(, )
.replace(, )
.replace(, )
.replace(, ))
Step 4: Format Placemark Descriptions
Create rich descriptions with property details:
def create_description(building):
desc_parts = []
if "address" in building:
desc_parts.append(f"<b>Address:</b> {building['address']}")
if "gfa" in building.get("metadata", {}):
desc_parts.append(f"<b>GFA:</b> {building['metadata']['gfa']:,} sq ft")
if "carbon_intensity" in building.get("metadata", {}):
desc_parts.append(f"<b>Carbon Intensity:</b> {building['metadata']['carbon_intensity']} kgCO2e/sqft/yr")
if "eui" in building.get("metadata", {}):
desc_parts.append(f"<b>EUI:</b> {building['metadata']['eui']} kWh/sqft/yr")
return "<br/>".join(desc_parts)
Step 5: Add Custom Styling (Optional)
Color code by property type:
<Style id="office">
<IconStyle>
<Icon>
<href>http://maps.google.com/mapfiles/kml/pushpin/blue-pushpin.png</href>
</Icon>
</IconStyle>
</Style>
<Style id="residential">
<IconStyle>
<Icon>
<href>http://maps.google.com/mapfiles/kml/pushpin/grn-pushpin.png</href>
</Icon>
</IconStyle>
</Style>
Icon colors available:
ylw-pushpin.png - Yellow (default)
red-pushpin.png - Red
blu-pushpin.png - Blue
grn-pushpin.png - Green
pink-pushpin.png - Pink
ltblu-pushpin.png - Light Blue
wht-pushpin.png - White
Step 6: Organize with Folders (Optional)
Group placemarks by category:
<Folder>
<name>Office Buildings</name>
<description>Portfolio office properties</description>
<Placemark>
<name>Building A</name>
...
</Placemark>
<Placemark>
<name>Building B</name>
...
</Placemark>
</Folder>
<Folder>
<name>Retail Properties</name>
<description>Portfolio retail properties</description>
<Placemark>
<name>Building C</name>
...
</Placemark>
</Folder>
Step 7: Validate and Export
Validation checks:
Test the KML:
If possible, open in Google Earth to verify:
- All placemarks appear at correct locations
- Descriptions display properly
- Icons render correctly
- Folders are organized as expected
Export:
with open("portfolio_locations.kml", "w", encoding="utf-8") as f:
f.write(kml_content)
print("KML file generated: portfolio_locations.kml")
print("You can open this file in:")
print("- Google Earth Desktop")
print("- Google Earth Pro")
print("- Google My Maps (File > Import)")
Example: Complete Workflow
User: "Create a KML file for these addresses: 350 5th Ave New York NY, 600 W Virginia St Seattle WA"
Step 1: Parse input
addresses = [
"350 5th Ave, New York, NY",
"600 W Virginia St, Seattle, WA"
]
Step 2: Geocode via Nominatim
import time
import requests
placemarks = []
for addr in addresses:
response = requests.get(
"https://nominatim.openstreetmap.org/search",
params={"format": "json", "q": addr},
headers={"User-Agent": "Audette-Orchestrator/1.0"}
)
if response.ok and response.json():
result = response.json()[0]
placemarks.append({
"name": addr.split(",")[0],
"description": addr,
"lat": float(result["lat"]),
"lng": float(result["lon"])
})
time.sleep(1)
Step 3: Generate KML
kml = generate_kml(
placemarks=placemarks,
document_name="Portfolio Locations",
description="Two building portfolio"
)
Step 4: Write file
with open("portfolio.kml", "w", encoding="utf-8") as f:
f.write(kml)
Result:
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<Document>
<name>Portfolio Locations</name>
<description>Two building portfolio</description>
<Placemark>
<name>350 5th Ave</name>
<description><![CDATA[350 5th Ave, New York, NY]]></description>
<Point>
<coordinates>-73.985428,40.748817,0</coordinates>
</Point>
</Placemark>
<Placemark>
<name>600 W Virginia St</name>
<description><![CDATA[600 W Virginia St, Seattle, WA]]></description>
<Point>
<coordinates>-122.3471,47.5691,0</coordinates>
</Point>
</Placemark>
</>
Error Handling
Geocoding Failure
Unable to geocode address: "[address]"
Please provide coordinates manually:
- Latitude (-90 to 90):
- Longitude (-180 to 180):
Invalid Coordinates
Invalid coordinates for "[name]":
- Latitude: [lat] (must be -90 to 90)
- Longitude: [lng] (must be -180 to 180)
Please correct and try again.
Rate Limit Exceeded
Nominatim rate limit reached (1 req/sec).
Processing addresses with 1-second delay between each...
[Progress: 5/20 addresses geocoded]
Important Rules
- Respect Nominatim rate limit: 1 request per second
- Always include User-Agent header with contact info
- Coordinates order is lng,lat (not lat,lng) in KML Point elements
- Escape XML special characters in names and descriptions
- Validate coordinates are within valid ranges before generating KML
- Cache geocoding results to avoid redundant API calls
- Test KML in Google Earth if possible before delivering to user
Testing Checklist
Before delivering KML file: