GIS for Humans Who Don’t Want to Read 500 Pages

Making Maps Without Losing Your Mind

code
GIS
analysis
Author

Someone Who’s Been There

Published

June 10, 2025

Welcome to GIS Hell (It’s Actually Pretty Cool)

GIS = Geographic Information Systems = “I put data on maps and somehow this became my job”

What you’ll learn: - How to not mess up coordinate systems (spoiler: you will anyway) - Make pretty maps that impress your boss - Pretend you know what you’re doing

Install This Stuff (Or Cry Later)

# R packages (install once, debug forever)
install.packages(c("sf", "leaflet", "tmap"))
# Python (because why make life easy?)
# pip install geopandas folium

Chapter 1: Your First Map (That Won’t Suck)

The Magic of Putting Dots on Maps

library(sf)
library(leaflet)

# Create some fake data (like your confidence)
places <- data.frame(
  location = c("Coffee Shop", "My House", "Regret"),
  lon = c(-122.4, -122.5, -122.6),
  lat = c(37.8, 37.9, 37.7)
)

# Turn it into spatial data (✨ magic ✨)
spatial_places <- st_as_sf(places, 
                          coords = c("lon", "lat"), 
                          crs = 4326)  # 4326 = "GPS coordinates"
import geopandas as gpd
from shapely.geometry import Point

# Same fake data, different language
data = {
    'location': ['Coffee Shop', 'My House', 'Regret'],
    'lon': [-122.4, -122.5, -122.6],
    'lat': [37.8, 37.9, 37.7]
}

# GeoPandas: Pandas but with existential crisis about location
gdf = gpd.GeoDataFrame(data, 
                      geometry=[Point(x, y) for x, y in zip(data['lon'], data['lat'])],
                      crs='EPSG:4326')

Make It Interactive (Impress People)

# Leaflet: Like Google Maps but you made it
leaflet(spatial_places) %>%
  addTiles() %>%  # Free maps (thanks OpenStreetMap!)
  addMarkers(popup = ~location) %>%
  setView(lng = -122.5, lat = 37.8, zoom = 10)
import folium

# Folium: Python's answer to "I want pretty maps"
m = folium.Map(location=[37.8, -122.5], zoom_start=10)

for idx, row in gdf.iterrows():
    folium.Marker([row.geometry.y, row.geometry.x], 
                 popup=row['location']).add_to(m)

m  # Boom. Map.

Chapter 2: Buffers (Circles Around Stuff)

Why buffers? “Everything within 1 mile of Starbucks” is a legitimate analysis.

# Transform to meters (because measuring in degrees is weird)
places_meters <- st_transform(spatial_places, crs = 3857)

# Make 500m circles around each point
buffers <- st_buffer(places_meters, dist = 500)

# Back to normal coordinates for mapping
buffers <- st_transform(buffers, crs = 4326)

# Add to map
leaflet() %>%
  addTiles() %>%
  addPolygons(data = buffers, color = "blue", fillOpacity = 0.3) %>%
  addMarkers(data = spatial_places, popup = ~location)
# Same thing, more typing
gdf_meters = gdf.to_crs('EPSG:3857')
buffers = gdf_meters.buffer(500)  # 500 meters
buffer_gdf = gpd.GeoDataFrame(gdf[['location']], 
                             geometry=buffers).to_crs('EPSG:4326')

# Add to map
m = folium.Map(location=[37.8, -122.5], zoom_start=10)

# Add buffers
for idx, row in buffer_gdf.iterrows():
    folium.GeoJson(row.geometry, 
                  style_function=lambda x: {'color': 'blue', 'fillOpacity': 0.3}).add_to(m)

# Add points
for idx, row in gdf.iterrows():
    folium.Marker([row.geometry.y, row.geometry.x], 
                 popup=row['location']).add_to(m)
m

Chapter 3: Distance (How Far is Too Far?)

# Calculate distances between all points
distances <- st_distance(spatial_places)
print("Distances in meters (approximately):")
print(distances)

# Convert to miles because America
distances_miles <- distances * 0.000621371
print("Distances in freedom units:")
print(distances_miles)
from sklearn.metrics.pairwise import haversine_distances
import numpy as np

# Extract coordinates and calculate distances
coords = np.radians([[p.y, p.x] for p in gdf.geometry])
distances = haversine_distances(coords) * 6371000  # Earth radius

print("Distances in meters:")
print(distances.astype(int))

Chapter 4: The Coordinate System Nightmare

Truth: You will spend 50% of your GIS career fixing coordinate systems.

Common CRS (Coordinate Reference Systems): - EPSG:4326 = GPS coordinates (lat/lon) - Use for web maps - EPSG:3857 = Web Mercator - Use for measurements - Local UTM zones - Use when you need to be precise

# Check what CRS you're using
st_crs(spatial_places)

# Transform between coordinate systems
utm_data <- st_transform(spatial_places, crs = 32610)  # UTM Zone 10N
print("Now in UTM coordinates:")
print(st_coordinates(utm_data))

Chapter 5: Quick Wins

Load Real Data (Finally!)

# Load a shapefile (if you have one)
# data <- st_read("your_file.shp")

# Or get data from the internet
library(osmdata)
# Get coffee shops in your area
# coffee <- opq("San Francisco, CA") %>%
#   add_osm_feature(key = "amenity", value = "cafe") %>%
#   osmdata_sf()

Export Your Masterpiece

# Save as shapefile
# st_write(spatial_places, "my_awesome_data.shp")

# Save as GeoJSON (the cool kids format)
# st_write(spatial_places, "my_awesome_data.geojson")

The End (You Survived!)

What you learned: - Points on maps = happiness - Buffers = circles around things - CRS = the source of all problems - GIS = surprisingly addictive

Next steps: - Make more maps - Break more coordinate systems
- Blame ESRI for everything - Profit???

Pro tip: When in doubt, reproject to EPSG:4326. When that doesn’t work, try EPSG:3857. When that fails, panic quietly.


“In GIS, all roads lead to Rome… if your coordinate system is correct.”