SlideShare a Scribd company logo
1 of 43
Download to read offline
+
terrafrost@php.net
Jim Wigginton
• Creator and maintainer of phpseclib/phpseclib library
• PHP for ~20 years
• Born and raised in Austin, TX
• terrafrost@php.net
Leaflet Setup
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.8.0/dist/leaflet.css"/>
<script src="https://unpkg.com/leaflet@1.8.0/dist/leaflet.js"></script>
<style>
body {
margin: 0;
}
</style>
<div id="map" style="width: 100%; height: 100%"></div>
<script>
var map = L.map('map').setView([51.505, -0.09], 13);
var tiles = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);
</script>
• The above is based off of https://leafletjs.com/examples/quick-start/
• [51.505, -09] is the latitude / longitude, 13 is the zoom
• Current location can be obtained by doing this:
• Real time location updates can be obtained by doing this:
navigator.geolocation.getCurrentPosition(pos => {
map.setView([pos.coords.latitude, pos.coords.longitude], 14);
});
navigator.geolocation.watchPosition(pos => {
polyline.addLatLng([pos.coords.latitude, pos.coords.longitude]);
});
Tile Sets
Mapbox Satellite OpenStreetMap + TomTom Traffic Flow
USGSTopo (MapServer) Mapbox Light + ISU Environmental Mesonet
Database Setup
1. Install the necessary software:
• Windows: Install OSGeo4W and then open "OSGeo4W Shell".
• Ubuntu: Run the following commands:
2. Download shapefiles from https://www.capmetro.org/metrolabs. Sort by "Recently Updated“
3. Import Routes.shp into MySQL:
sudo add-apt-repository ppa:ubuntugis/ppa
sudo apt-get update
sudo apt-get install gdal-bin
sudo apt-get install libgdal-dev
ogr2ogr -f MySQL MySQL:dbname,host=localhost,user=user,password=pass 
Routes.shp -nln tablename -update -append 
-t_srs "EPSG:4326" -lco engine=InnoDB -skipfailures
Coordinate Systems
Coordinate System Austin, TX Example
EPSG:4326 WGS 84 30.267222, -97.743056 TxDOT
EPSG:4269 NAD83 30.260336, -97.7458308 NHD, TIGER
EPSG:3857 WGS 84 / Pseudo-Mercator 3537945.1867267964, -10880707.234867256 US DOT
EPSG:3081 NAD83 / Texas State Mapping System 902702.814304697, 1216728.9256139707 THC
EPSG:32614 WGS 84 / UTM zone 14N 3349064.8428058745, 620908.0252681801 CapMetro
Conversions done with https://epsg.io/transform
Database
OGR_FID SHAPE route_id routename direction routecolor textcolor routetype routetheme servicenm servicetyp sign_id service_id source sourcedate
1 BLOB 1
North Lamar/South
Congress
Southbound 004A97 FFFFFF Local NULL Weekday Weekday 153 1-153
Capital
Metro
6/3/2022
2 BLOB 324 Georgian/Ohlen Westbound 004A97 FFFFFF Crosstown NULL Sunday Sunday 153 5-153
Capital
Metro
6/3/2022
3 BLOB 19 Bull Creek Northbound 004A97 FFFFFF Local NULL Sunday Sunday 153 5-153
Capital
Metro
6/3/2022
4 BLOB 243 Wells Branch Westbound 004A97 FFFFFF Feeder NULL Sunday Sunday 153 5-153
Capital
Metro
6/3/2022
5 BLOB 6 East 12th Westbound 004A97 FFFFFF Local NULL Saturday Saturday 153 4-153
Capital
Metro
6/3/2022
6 BLOB 310 Parker/Wickersham Eastbound 004A97 FFFFFF Crosstown NULL Weekday Weekday 153 1-153
Capital
Metro
6/3/2022
7 BLOB 339 Tuscany Westbound 004A97 FFFFFF Crosstown NULL Saturday Saturday 153 4-153
Capital
Metro
6/3/2022
8 BLOB 243 Wells Branch Eastbound 004A97 FFFFFF Feeder NULL Weekday Weekday 153 1-153
Capital
Metro
6/3/2022
9 BLOB 322 Chicon/Cherrywood Southbound 004A97 FFFFFF Crosstown NULL Sunday Sunday 153 5-153
Capital
Metro
6/3/2022
10 BLOB 550 Metro Rail Red Line Northbound E2231A FFFFFF Rail NULL RAIL AFC 2 Other 153 55004-153
Capital
Metro
6/3/2022
GeoJSON
1. Export from the DB with this SQL:
2. Load the GeoJSON in Leaflet by doing this:
SELECT ST_AsGeoJSON(SHAPE), servicenm, servicetyp
FROM capmetro_routes
WHERE route_id = 550
AND servicenm = '6TRAINMON to THURS';
L.geoJSON(route).addTo(map);
GeoJSON Primitives
Type Example
Point
{
"type": "Point",
"coordinates": [30.0, 10.0]
}
LineString
{
"type": "LineString",
"coordinates": [ [30.0, 10.0], [10.0, 30.0], [40.0, 40.0] ]
}
Polygon
{
"type": "Polygon",
"coordinates": [ [[30.0, 10.0], [40.0, 40.0], [20.0, 40.0], [10.0, 20.0], [30.0, 10.0]] ]
}
{
"type": "Polygon",
"coordinates": [
[[35.0, 10.0], [45.0, 45.0], [15.0, 40.0],
[10.0, 20.0], [35.0, 10.0]], [[20.0, 30.0], [35.0, 35.0], [30.0, 20.0], [20.0, 30.0]]
]
}
Markers
1. Export from the DB with this SQL:
2. Load the GeoJSON in Leaflet by doing this:
SELECT
stop_name,
CONCAT(latitude, ',', longitude) AS pos
FROM capmetro_stops
WHERE stop_type = 'Rail Station';
L.marker([30.264843,-97.738448])
.bindPopup('Downtown Station’)
.addTo(map);
GTFS Realtime
• General Transit Feed Specification
• Developed by Google in 2006
• Uses Protocol Buffers
• Get the download link for "CapMetro Vehicle Positions PB File"
from https://www.capmetro.org/metrolabs. Sort by "Recently Updated"
• Updated every 15s
• composer require google/gtfs-realtime-bindings
• Deprecated: As of February 2019, the official google-protobuf
Google protoc tool doesn’t support proto2 files. As a result we are
deprecating the PHP bindings until official support for proto2 files is
implemented in the Google protocol buffer tools.
GTFS Realtime: Server Side
<?php
require_once 'vendor/autoload.php’;
use transit_realtimeFeedMessage;
$output = [];
$data = file_get_contents('https://data.texas.gov/download/eiei-9rpf/application%2Foctet-stream’);
$feed = new FeedMessage();
$feed->parse($data);
foreach ($feed->getEntityList() as $entity) {
if ($entity->vehicle->trip && $entity->vehicle->trip->route_id == 550) {
$pos = $entity->vehicle->position; $output[] = [
'latitude' => $pos->latitude,
'longitude' => $pos->longitude,
'bearing' => $pos->bearing,
'speed' => $pos->speed
];
}
}
echo json_encode($output);
GTFS Realtime: Client Side
var realtimeUpdate = function() {
fetch('realtime.php’)
.then(response => response.json())
.then(data => {
data.forEach(train => {
var arrow = new L.Icon({
iconUrl: 'arrow-up.svg’,
iconSize: [25,28.975],
iconAnchor: [13, 0],
});
var marker = L.marker(
[train.latitude, train.longitude],
{icon: arrow, rotationAngle: train.bearing}
)
.bindPopup('<strong>Coordinates</strong>: ‘ +
train.latitude + ',' + train.longitude +
'<br><strong>Bearing</strong>: ' + train.bearing +
'<br><strong>Speed</strong>: ' + train.speed)
.addTo(map);
});
});
}
realtimeUpdate();
setInterval(realtimeUpdate, 15000);
Putting It Together
• arrow-up.svg is from Font Awesome ( ) and was
edited with Inkscape
• https://github.com/bbecquet/Leaflet.RotatedMar
ker is used to rotate the arrow
• NYC: https://api.mta.info/
• Los Angeles: https://developer.metro.net/api/
• Chicago: https://www.transitchicago.com/developers/bustracker/
• Houston: https://api-portal.ridemetro.org/
• London: https://api.tfl.gov.uk/
• France: https://prim.iledefrance-mobilites.fr/fr
Austin Western Railroad
• Download and import shapefile from https://hub.arcgis.com/datasets/fedmaps::north-american-rail-
lines-1/explore
• Query:
SELECT *
FROM trains
WHERE rrowner1 = 'AWRR';
OGR_FID SHAPE objectid fraarcid frfranode tofranode cntyfips stateab country rrowner1 trkrghts1 subdiv passngr tracks net miles km shape_leng
123293 BLOB 123293 423812 360530 360533 287 TX US AWRR NULL
GIDDINGS
INDUSTRIA
L SPUR
NULL 1 I 0.040001 0.064375 0.000666
123294 BLOB 123294 423813 360533 360541 287 TX US AWRR NULL
GIDDINGS
INDUSTRIA
L SPUR
NULL 1 I 0.144155 0.231995 0.002261
123386 BLOB 123386 423905 355408 355502 453 TX US AWRR CMRX CENTRAL C 1 M 1.476048 2.375474 0.023111
123413 BLOB 123413 423932 355403 355406 453 TX US AWRR NULL NULL NULL 1 M 0.334526 0.538368 0.004952
123604 BLOB 123604 424123 353382 353376 53 TX US AWRR NULL MAIN LINE NULL 0 O 0.350358 0.563848 0.005183
123705 BLOB 123705 424224 358097 358085 21 TX US AWRR NULL NULL NULL 0 O 0.143777 0.231386 0.002201
123706 BLOB 123706 424225 358629 358637 21 TX US AWRR NULL NULL NULL 0 O 0.073028 0.117527 0.001145
123707 BLOB 123707 424226 358637 358651 21 TX US AWRR NULL NULL NULL 0 O 0.223662 0.35995 0.003704
123708 BLOB 123708 424227 358637 358648 21 TX US AWRR NULL NULL NULL 0 O 0.194351 0.312778 0.003207
124262 BLOB 124262 424783 355418 355416 453 TX US AWRR NULL NULL NULL 1 M 0.204555 0.329199 0.003038
GeoJSON Multipart Geometries
Type Example
MultiPoint
{
"type": "MultiPoint",
"coordinates": [ [10.0, 40.0], [40.0, 30.0], [20.0, 20.0], [30.0, 10.0] ]
}
MultiLineString
{
"type": "MultiLineString",
"coordinates": [ [[10.0, 10.0], [20.0, 20.0], [10.0, 40.0]], [[40.0, 40.0], [30.0, 30.0], [40.0, 20.0], [30.0, 10.0]] ]
}
MultiPolygon
{
"type": "MultiPolygon",
"coordinates": [
[
[[30.0, 20.0], [45.0, 40.0], [10.0, 40.0], [30.0, 20.0]]
],
[
[[15.0, 5.0], [40.0, 10.0], [10.0, 20.0], [5.0, 10.0], [15.0, 5.0]]
]
]
}
GeoJSON Multipart Geometries: Part 2
Type Example
GeometryCollection
{
"type": "GeometryCollection",
"geometries": [
{
"type": "Point",
"coordinates": [40.0, 10.0]
},
{
"type": "LineString",
"coordinates": [ [10.0, 10.0], [20.0, 20.0], [10.0, 40.0] ]
},
{
"type": "Polygon",
"coordinates": [ [[40.0, 40.0], [20.0, 45.0], [45.0, 30.0], [40.0, 40.0]] ]
}
]
}
Styles
• Colorize layers:
• Colorize markers:
Colorized markers are from https://github.com/pointhi/leaflet-color-markers
L.geoJSON(route, {
color: 'red’,
}).addTo(map);
var redIcon = new L.Icon({
iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-red.png’,
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png’,
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
});
L.marker([30.264843,-97.738448], {icon: redIcon}).bindPopup('Downtown Station').addTo(map);
The Result
More Styles
• Bring train location markers to the front:
• Colorize markers:
• Add Control Layer:
var marker = L.marker(
[train.latitude, train.longitude],
{icon: arrow, rotationAngle: train.bearing, zIndexOffset: 1000}
);
var stations = L.layerGroup();
stations.addLayer(L.marker([30.264843,-97.738448]).bindPopup('Downtown Station’));
stations.addTo(map);
layerControl = L.control.layers().addTo(map);
layerControl.addOverlay(stations, 'Stations');
The Result
Counties
• Download and import "Counties (and equivalent)" shapefile from https://www.census.gov/cgi-
bin/geo/shapefiles/index.php
• TIGER: Topologically Integrated Geographic Encoding and Referencing
• Query:
SELECT *
FROM counties
WHERE statefp = 48;
OGR_FID SHAPE statefp countyfp countyns geoid name namelsad lsad aland awater intptlat intptlon
8BLOB 48327 1383949 48327 Menard Menard County 6 2.34E+09 613559 30.88527 -99.8589
12BLOB 48189 1383880 48189 Hale Hale County 6 2.6E+09 246678 34.06844 -101.823
14BLOB 4811 1383791 48011 Armstrong
Armstrong
County
6 2.35E+09 12183672 34.96418 -101.357
36BLOB 4857 1383814 48057 Calhoun Calhoun County 6 1.31E+09 1.36E+09 28.44172 -96.5796
39BLOB 4877 1383824 48077 Clay Clay County 6 2.82E+09 72506860 33.7859 -98.2129
56BLOB 48361 1383966 48361 Orange Orange County 6 8.65E+08 1.18E+08 30.12232 -93.8941
64BLOB 48177 1383874 48177 Gonzales
Gonzales
County
6 2.76E+09 8204086 29.46191 -97.4919
69BLOB 48147 1383859 48147 Fannin Fannin County 6 2.31E+09 20847065 33.59116 -96.105
71BLOB 48265 1383918 48265 Kerr Kerr County 6 2.86E+09 10231764 30.05995 -99.3533
100BLOB 48391 1383981 48391 Refugio Refugio County 6 2E+09 1.24E+08 28.32212 -97.1625
Counties: Client Side
var exclude = [], counties = L.layerGroup(), temp;
layerControl.addOverlay(counties, 'Counties’);
getCounties(map.getBounds());
map.on('moveend', function() {
getCounties(map.getBounds());
});
function getCounties(bounds) {
var ne = bounds.getNorthEast().lat + ',' + bounds.getNorthEast().lng;
var sw = bounds.getSouthWest().lat + ',' + bounds.getSouthWest().lng;
fetch('counties.php?ne=' + ne + '&sw=' + sw + '&exclude=' + exclude.join(',’))
.then(response => response.json())
.then(data => {
data.forEach(county => {
counties.addLayer(temp = L.geoJson(county.shape, {fill: false, color: "gray"}));
counties.addLayer(
L.marker(temp.getBounds().getCenter(), {opacity: 0})
.bindTooltip(county.name, {permanent: true, direction: 'center', className: 'countyName’})
);
exclude.push(county.fips);
});
counties.addTo(map);
});
}
Counties: Server Side
<?php
$db = new PDO('mysql:dbname=dbname;host=host', 'user', 'pass’);
$ne = explode(',', $_GET['ne’]);
$sw = explode(',', $_GET['sw’]);
$shape = "Polygon((
$sw[0] $sw[1],
$ne[0] $sw[1],
$ne[0] $ne[1],
$sw[0] $ne[1],
$sw[0] $sw[1]
))";
$sql_where = ‘’;
$exclude = [];
if (strlen($_GET['exclude'])) {
$exclude = explode(',', $_GET['exclude’]);
$sql_in = array_fill(0, count($exclude), '?’);
$sql_in = implode(',', $sql_in);
$sql_where = "AND countyfp NOT IN ($sql_in)";
}
$q = $db->prepare(“
SELECT
name,
countyfp AS fips,
ST_AsGeoJson(SHAPE) AS shape
FROM county_shapes
WHERE MBRIntersects(ST_GeomFromText(?, 4269), SHAPE)
AND statefp = 48
AND ST_Area(ST_GeomFromText(?, 4269)) < 40075210158
$sql_where
");
$q->execute(array_merge([$shape, $shape], $exclude));
$result = [];
while ($row = $q->fetch(PDO::FETCH_ASSOC)) {
$row['shape'] = json_decode($row['shape’]);
$result[] = $row;
}
echo json_encode($result);
The Result
The Narrows
The Narrows: Mapped
Polygon Labels
var countyNames;
function labelCounties() {
var bounds = map.getBounds();
if (countyNames) {
map.removeLayer(countyNames);
}
countyNames = L.layerGroup();
for (const [name, layer] of Object.entries(countyLayers)) {
var newCoords = [], coords = countyLayers[name].toGeoJSON()['features'][0]['geometry']['coordinates'][0]
coords.forEach(function (coord) {
coord = L.latLng(coord[1], coord[0]);
coord.lat = Math.min(coord.lat, bounds.getNorthWest().lat);
coord.lat = Math.max(coord.lat, bounds.getSouthEast().lat);
coord.lng = Math.max(coord.lng, bounds.getNorthWest().lng);
coord.lng = Math.min(coord.lng, bounds.getSouthEast().lng);
newCoords.push(coord);
});
var polygon = L.polygon(newCoords, {fill: false, opacity: 0}).addTo(map);
marker = L.marker(polygon.getCenter(), {opacity: 0}).bindTooltip(name, {permanent: true, direction: 'center', className: 'countyName’});
countyNames.addLayer(marker);
map.removeLayer(polygon);
}
countyNames.addTo(map);
}
labelCounties();
map.on('moveend', function() {
labelCounties();
});
Polygon Labels Visualized
The Narrows: With Counties
Labels for Lines
• Using https://github.com/3mapslab/Leaflet.streetlabels
• Merge LineString’s:
• Wrap GeoJSON:
$coords = $rows[0];
unset($rows[0]);
while (count($rows)) {
foreach ($rows as $i => $line) {
if ($coords[count($coords) - 1] == $line[0]) {
$coords = array_merge($coords, array_slice($line, 1));
unset($rows[$i]);
} else if ($coords[0] == $line[count($line) - 1]) {
$coords = array_merge(array_slice($line, 0, -1), $coords);
unset($rows[$i]);
}
}
}
function featureWrapper(geometry, name) {
return feature = {
type: 'Feature’,
properties: {name: name},
geometry: geometry
};
}
$coords = [
'type' => 'LineString’,
'coordinates' => $cords
];
The Narrows: With Labeled Lines
Third Street Railroad Trestle
Sanborn Fire Insurance Maps
Georeferencing
Georeferencing
1 Choose Tool
• ArcGIS
• QGIS
• Georeferencer.com
• MapWarper.net
3 Place Image
1. Extract bounding coordinates:
2. Convert to PNG or WebP
3. Place on map:
gdalinfo exported.tiff
L.imageOverlay(
'exported.webp’, [
// top left
[30.2725146, -97.7578187],
// bottom right
[30.2644510, -97.7496347]
],
{opacity: 0.5}
).addTo(map);
2 Basic Technique
The Result
Merging Overlapping GeoTIFFs
• Perform the merge:
• "In areas of overlap, the last image will be copied over earlier ones"
• Copy nextPage.tif as a new layer over temp.tif and trim away at nextPage.tif
• Copy the trimmed layer back to nextPage.tif, delete the old layer, and save as new.tif.
GeoTIFF data will be lost.
gdal_merge -o temp.tif nextPage.tif prevPage.tif
Creating Tile Layers
1
2
3
Copy GeoTIFF data from orig.tif to new.tif
listgeo -no_norm orig.tif > orig.geo
geotifcp -g orig.geo new.tif temp.tif
Merge the TIFFs
gdal_merge -o merged.tif master.tif temp.tif
Rm master.tif; rm temp.tif; mv merged.tif master.tif
Create Tile Layer
Gdal2tiles master.tif
4 Use Tile Layer
L.tileLayer('https://domain.tld/sanborn/{z}/{x}/{y}.png’, {
tms: 1,
opacity: 0.7,
minZoom: 13,
maxZoom: 19
});
The Result
Tile Layer Caveats
"OSM does NOT pre-render every tile. Pre-rendering all tiles would use around 54
TB of storage. As the following table shows, the majority of tiles are never viewed.
In fact just 1.79% are viewed. It works out this way because the majority of tiles
are at zoom level 18 and actually the majority contain nothing of interest. By
following an on-the-fly rendering approach we can avoid rendering these tiles
unnecessarily. The tile view count column shows how many tiles have been
produced on the OSM Tile server."
Source: https://wiki.openstreetmap.org/wiki/Tile_disk_usage
Thank You
• Slides & Feedback:
https://joind.in/talk/44a76
• Questions? terrafrost@php.net

More Related Content

What's hot

gRPC and Microservices
gRPC and MicroservicesgRPC and Microservices
gRPC and MicroservicesJonathan Gomez
 
Advanced JavaScript
Advanced JavaScriptAdvanced JavaScript
Advanced JavaScriptNascenia IT
 
LX 공간정보아카데미 PostGIS 강의자료
LX 공간정보아카데미 PostGIS 강의자료LX 공간정보아카데미 PostGIS 강의자료
LX 공간정보아카데미 PostGIS 강의자료JungHwan Yun
 
Programming with Python and PostgreSQL
Programming with Python and PostgreSQLProgramming with Python and PostgreSQL
Programming with Python and PostgreSQLPeter Eisentraut
 
Angular and The Case for RxJS
Angular and The Case for RxJSAngular and The Case for RxJS
Angular and The Case for RxJSSandi Barr
 
Getting Started with Consul
Getting Started with ConsulGetting Started with Consul
Getting Started with ConsulRamit Surana
 
공간정보거점대학 - PyQGIS 및 플러그인 개발
공간정보거점대학 - PyQGIS 및 플러그인 개발공간정보거점대학 - PyQGIS 및 플러그인 개발
공간정보거점대학 - PyQGIS 및 플러그인 개발MinPa Lee
 
Puppeteer can automate that! - Frontmania
Puppeteer can automate that! - FrontmaniaPuppeteer can automate that! - Frontmania
Puppeteer can automate that! - FrontmaniaÖnder Ceylan
 
Best Practices in Qt Quick/QML - Part IV
Best Practices in Qt Quick/QML - Part IVBest Practices in Qt Quick/QML - Part IV
Best Practices in Qt Quick/QML - Part IVICS
 
Open Source GIS 기초교육 4일차 - GeoServer 기초 2014년 7월판
Open Source GIS 기초교육 4일차 - GeoServer 기초 2014년 7월판Open Source GIS 기초교육 4일차 - GeoServer 기초 2014년 7월판
Open Source GIS 기초교육 4일차 - GeoServer 기초 2014년 7월판BJ Jang
 
QGIS 고급 및 PyQGIS - 김기웅, 임영현
QGIS 고급 및 PyQGIS - 김기웅, 임영현 QGIS 고급 및 PyQGIS - 김기웅, 임영현
QGIS 고급 및 PyQGIS - 김기웅, 임영현 SANGHEE SHIN
 
Golang basics for Java developers - Part 1
Golang basics for Java developers - Part 1Golang basics for Java developers - Part 1
Golang basics for Java developers - Part 1Robert Stern
 
Mongo DB 완벽가이드 - 4장 쿼리하기
Mongo DB 완벽가이드 - 4장 쿼리하기Mongo DB 완벽가이드 - 4장 쿼리하기
Mongo DB 완벽가이드 - 4장 쿼리하기JangHyuk You
 
VueJS Introduction
VueJS IntroductionVueJS Introduction
VueJS IntroductionDavid Ličen
 
NATS for Rubyists - Tokyo Rubyist Meetup
NATS for Rubyists - Tokyo Rubyist MeetupNATS for Rubyists - Tokyo Rubyist Meetup
NATS for Rubyists - Tokyo Rubyist Meetupwallyqs
 

What's hot (20)

gRPC and Microservices
gRPC and MicroservicesgRPC and Microservices
gRPC and Microservices
 
Concurrency With Go
Concurrency With GoConcurrency With Go
Concurrency With Go
 
Advanced JavaScript
Advanced JavaScriptAdvanced JavaScript
Advanced JavaScript
 
LX 공간정보아카데미 PostGIS 강의자료
LX 공간정보아카데미 PostGIS 강의자료LX 공간정보아카데미 PostGIS 강의자료
LX 공간정보아카데미 PostGIS 강의자료
 
Programming with Python and PostgreSQL
Programming with Python and PostgreSQLProgramming with Python and PostgreSQL
Programming with Python and PostgreSQL
 
Accred1.xls
Accred1.xlsAccred1.xls
Accred1.xls
 
Angular and The Case for RxJS
Angular and The Case for RxJSAngular and The Case for RxJS
Angular and The Case for RxJS
 
Getting Started with Consul
Getting Started with ConsulGetting Started with Consul
Getting Started with Consul
 
공간정보거점대학 - PyQGIS 및 플러그인 개발
공간정보거점대학 - PyQGIS 및 플러그인 개발공간정보거점대학 - PyQGIS 및 플러그인 개발
공간정보거점대학 - PyQGIS 및 플러그인 개발
 
Puppeteer can automate that! - Frontmania
Puppeteer can automate that! - FrontmaniaPuppeteer can automate that! - Frontmania
Puppeteer can automate that! - Frontmania
 
Best Practices in Qt Quick/QML - Part IV
Best Practices in Qt Quick/QML - Part IVBest Practices in Qt Quick/QML - Part IV
Best Practices in Qt Quick/QML - Part IV
 
Pydata-Python tools for webscraping
Pydata-Python tools for webscrapingPydata-Python tools for webscraping
Pydata-Python tools for webscraping
 
Open Source GIS 기초교육 4일차 - GeoServer 기초 2014년 7월판
Open Source GIS 기초교육 4일차 - GeoServer 기초 2014년 7월판Open Source GIS 기초교육 4일차 - GeoServer 기초 2014년 7월판
Open Source GIS 기초교육 4일차 - GeoServer 기초 2014년 7월판
 
Introduction to NodeJS
Introduction to NodeJSIntroduction to NodeJS
Introduction to NodeJS
 
QGIS 고급 및 PyQGIS - 김기웅, 임영현
QGIS 고급 및 PyQGIS - 김기웅, 임영현 QGIS 고급 및 PyQGIS - 김기웅, 임영현
QGIS 고급 및 PyQGIS - 김기웅, 임영현
 
Golang basics for Java developers - Part 1
Golang basics for Java developers - Part 1Golang basics for Java developers - Part 1
Golang basics for Java developers - Part 1
 
Mongo DB 완벽가이드 - 4장 쿼리하기
Mongo DB 완벽가이드 - 4장 쿼리하기Mongo DB 완벽가이드 - 4장 쿼리하기
Mongo DB 완벽가이드 - 4장 쿼리하기
 
Socks Over RDP
Socks Over RDPSocks Over RDP
Socks Over RDP
 
VueJS Introduction
VueJS IntroductionVueJS Introduction
VueJS Introduction
 
NATS for Rubyists - Tokyo Rubyist Meetup
NATS for Rubyists - Tokyo Rubyist MeetupNATS for Rubyists - Tokyo Rubyist Meetup
NATS for Rubyists - Tokyo Rubyist Meetup
 

Similar to Austin Transit Map

Handling Real-time Geostreams
Handling Real-time GeostreamsHandling Real-time Geostreams
Handling Real-time Geostreamsguest35660bc
 
Handling Real-time Geostreams
Handling Real-time GeostreamsHandling Real-time Geostreams
Handling Real-time GeostreamsRaffi Krikorian
 
Basics of html5, data_storage, css3
Basics of html5, data_storage, css3Basics of html5, data_storage, css3
Basics of html5, data_storage, css3Sreejith Nair
 
Inspec one tool to rule them all
Inspec one tool to rule them allInspec one tool to rule them all
Inspec one tool to rule them allKimball Johnson
 
Java bytecode Malware Analysis
Java bytecode Malware AnalysisJava bytecode Malware Analysis
Java bytecode Malware AnalysisBrian Baskin
 
XML-Free Programming
XML-Free ProgrammingXML-Free Programming
XML-Free ProgrammingStephen Chin
 
Website Performance Basics
Website Performance BasicsWebsite Performance Basics
Website Performance Basicsgeku
 
Five Pound App talk: hereit.is, Web app architecture, REST, CSS3
Five Pound App talk: hereit.is, Web app architecture, REST, CSS3Five Pound App talk: hereit.is, Web app architecture, REST, CSS3
Five Pound App talk: hereit.is, Web app architecture, REST, CSS3Jamie Matthews
 
Illuminated Hacks -- Where 2.0 101 Tutorial
Illuminated Hacks -- Where 2.0 101 TutorialIlluminated Hacks -- Where 2.0 101 Tutorial
Illuminated Hacks -- Where 2.0 101 Tutorialmikel_maron
 
Smashing the stats for fun (and profit)
Smashing the stats for fun (and profit)Smashing the stats for fun (and profit)
Smashing the stats for fun (and profit)Security B-Sides
 
AtlasCamp 2015 Docker continuous integration training
AtlasCamp 2015 Docker continuous integration trainingAtlasCamp 2015 Docker continuous integration training
AtlasCamp 2015 Docker continuous integration trainingSteve Smith
 
Agile Tour Shanghai December 2011
Agile Tour Shanghai December 2011Agile Tour Shanghai December 2011
Agile Tour Shanghai December 2011Alistair McKinnell
 
How I make a podcast website using serverless technology in 2023
How I make a podcast website using serverless technology in 2023How I make a podcast website using serverless technology in 2023
How I make a podcast website using serverless technology in 2023Shengyou Fan
 
Microformats: what are they and why do I care?
Microformats: what are they and why do I care?Microformats: what are they and why do I care?
Microformats: what are they and why do I care?adactio
 

Similar to Austin Transit Map (20)

CEI Email 3.14.03
CEI Email 3.14.03CEI Email 3.14.03
CEI Email 3.14.03
 
Handling Real-time Geostreams
Handling Real-time GeostreamsHandling Real-time Geostreams
Handling Real-time Geostreams
 
Handling Real-time Geostreams
Handling Real-time GeostreamsHandling Real-time Geostreams
Handling Real-time Geostreams
 
HTML5 - Pedro Rosa
HTML5 - Pedro RosaHTML5 - Pedro Rosa
HTML5 - Pedro Rosa
 
Ruby Robots
Ruby RobotsRuby Robots
Ruby Robots
 
Basics of html5, data_storage, css3
Basics of html5, data_storage, css3Basics of html5, data_storage, css3
Basics of html5, data_storage, css3
 
Inspec one tool to rule them all
Inspec one tool to rule them allInspec one tool to rule them all
Inspec one tool to rule them all
 
Java bytecode Malware Analysis
Java bytecode Malware AnalysisJava bytecode Malware Analysis
Java bytecode Malware Analysis
 
XML-Free Programming
XML-Free ProgrammingXML-Free Programming
XML-Free Programming
 
Explain this!
Explain this!Explain this!
Explain this!
 
Website Performance Basics
Website Performance BasicsWebsite Performance Basics
Website Performance Basics
 
Five Pound App talk: hereit.is, Web app architecture, REST, CSS3
Five Pound App talk: hereit.is, Web app architecture, REST, CSS3Five Pound App talk: hereit.is, Web app architecture, REST, CSS3
Five Pound App talk: hereit.is, Web app architecture, REST, CSS3
 
Illuminated Hacks -- Where 2.0 101 Tutorial
Illuminated Hacks -- Where 2.0 101 TutorialIlluminated Hacks -- Where 2.0 101 Tutorial
Illuminated Hacks -- Where 2.0 101 Tutorial
 
Smashing the stats for fun (and profit)
Smashing the stats for fun (and profit)Smashing the stats for fun (and profit)
Smashing the stats for fun (and profit)
 
AtlasCamp 2015 Docker continuous integration training
AtlasCamp 2015 Docker continuous integration trainingAtlasCamp 2015 Docker continuous integration training
AtlasCamp 2015 Docker continuous integration training
 
Agile Tour Shanghai December 2011
Agile Tour Shanghai December 2011Agile Tour Shanghai December 2011
Agile Tour Shanghai December 2011
 
How I make a podcast website using serverless technology in 2023
How I make a podcast website using serverless technology in 2023How I make a podcast website using serverless technology in 2023
How I make a podcast website using serverless technology in 2023
 
Czzawk
CzzawkCzzawk
Czzawk
 
Next Level Curl
Next Level CurlNext Level Curl
Next Level Curl
 
Microformats: what are they and why do I care?
Microformats: what are they and why do I care?Microformats: what are they and why do I care?
Microformats: what are they and why do I care?
 

Recently uploaded

Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsMaria Levchenko
 
Maximizing Board Effectiveness 2024 Webinar.pptx
Maximizing Board Effectiveness 2024 Webinar.pptxMaximizing Board Effectiveness 2024 Webinar.pptx
Maximizing Board Effectiveness 2024 Webinar.pptxOnBoard
 
Pigging Solutions in Pet Food Manufacturing
Pigging Solutions in Pet Food ManufacturingPigging Solutions in Pet Food Manufacturing
Pigging Solutions in Pet Food ManufacturingPigging Solutions
 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slidespraypatel2
 
AI as an Interface for Commercial Buildings
AI as an Interface for Commercial BuildingsAI as an Interface for Commercial Buildings
AI as an Interface for Commercial BuildingsMemoori
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerThousandEyes
 
Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationRadu Cotescu
 
Install Stable Diffusion in windows machine
Install Stable Diffusion in windows machineInstall Stable Diffusion in windows machine
Install Stable Diffusion in windows machinePadma Pradeep
 
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024BookNet Canada
 
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...Neo4j
 
Factors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptxFactors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptxKatpro Technologies
 
SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024Scott Keck-Warren
 
Salesforce Community Group Quito, Salesforce 101
Salesforce Community Group Quito, Salesforce 101Salesforce Community Group Quito, Salesforce 101
Salesforce Community Group Quito, Salesforce 101Paola De la Torre
 
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking MenDelhi Call girls
 
IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsEnterprise Knowledge
 
SIEMENS: RAPUNZEL – A Tale About Knowledge Graph
SIEMENS: RAPUNZEL – A Tale About Knowledge GraphSIEMENS: RAPUNZEL – A Tale About Knowledge Graph
SIEMENS: RAPUNZEL – A Tale About Knowledge GraphNeo4j
 
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...HostedbyConfluent
 
[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdfhans926745
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationSafe Software
 
The Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxThe Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxMalak Abu Hammad
 

Recently uploaded (20)

Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed texts
 
Maximizing Board Effectiveness 2024 Webinar.pptx
Maximizing Board Effectiveness 2024 Webinar.pptxMaximizing Board Effectiveness 2024 Webinar.pptx
Maximizing Board Effectiveness 2024 Webinar.pptx
 
Pigging Solutions in Pet Food Manufacturing
Pigging Solutions in Pet Food ManufacturingPigging Solutions in Pet Food Manufacturing
Pigging Solutions in Pet Food Manufacturing
 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slides
 
AI as an Interface for Commercial Buildings
AI as an Interface for Commercial BuildingsAI as an Interface for Commercial Buildings
AI as an Interface for Commercial Buildings
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organization
 
Install Stable Diffusion in windows machine
Install Stable Diffusion in windows machineInstall Stable Diffusion in windows machine
Install Stable Diffusion in windows machine
 
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
 
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
 
Factors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptxFactors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptx
 
SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024
 
Salesforce Community Group Quito, Salesforce 101
Salesforce Community Group Quito, Salesforce 101Salesforce Community Group Quito, Salesforce 101
Salesforce Community Group Quito, Salesforce 101
 
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
 
IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI Solutions
 
SIEMENS: RAPUNZEL – A Tale About Knowledge Graph
SIEMENS: RAPUNZEL – A Tale About Knowledge GraphSIEMENS: RAPUNZEL – A Tale About Knowledge Graph
SIEMENS: RAPUNZEL – A Tale About Knowledge Graph
 
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...
 
[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
 
The Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxThe Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptx
 

Austin Transit Map

  • 2. Jim Wigginton • Creator and maintainer of phpseclib/phpseclib library • PHP for ~20 years • Born and raised in Austin, TX • terrafrost@php.net
  • 3.
  • 4. Leaflet Setup <link rel="stylesheet" href="https://unpkg.com/leaflet@1.8.0/dist/leaflet.css"/> <script src="https://unpkg.com/leaflet@1.8.0/dist/leaflet.js"></script> <style> body { margin: 0; } </style> <div id="map" style="width: 100%; height: 100%"></div> <script> var map = L.map('map').setView([51.505, -0.09], 13); var tiles = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map); </script> • The above is based off of https://leafletjs.com/examples/quick-start/ • [51.505, -09] is the latitude / longitude, 13 is the zoom • Current location can be obtained by doing this: • Real time location updates can be obtained by doing this: navigator.geolocation.getCurrentPosition(pos => { map.setView([pos.coords.latitude, pos.coords.longitude], 14); }); navigator.geolocation.watchPosition(pos => { polyline.addLatLng([pos.coords.latitude, pos.coords.longitude]); });
  • 5. Tile Sets Mapbox Satellite OpenStreetMap + TomTom Traffic Flow USGSTopo (MapServer) Mapbox Light + ISU Environmental Mesonet
  • 6. Database Setup 1. Install the necessary software: • Windows: Install OSGeo4W and then open "OSGeo4W Shell". • Ubuntu: Run the following commands: 2. Download shapefiles from https://www.capmetro.org/metrolabs. Sort by "Recently Updated“ 3. Import Routes.shp into MySQL: sudo add-apt-repository ppa:ubuntugis/ppa sudo apt-get update sudo apt-get install gdal-bin sudo apt-get install libgdal-dev ogr2ogr -f MySQL MySQL:dbname,host=localhost,user=user,password=pass Routes.shp -nln tablename -update -append -t_srs "EPSG:4326" -lco engine=InnoDB -skipfailures
  • 7. Coordinate Systems Coordinate System Austin, TX Example EPSG:4326 WGS 84 30.267222, -97.743056 TxDOT EPSG:4269 NAD83 30.260336, -97.7458308 NHD, TIGER EPSG:3857 WGS 84 / Pseudo-Mercator 3537945.1867267964, -10880707.234867256 US DOT EPSG:3081 NAD83 / Texas State Mapping System 902702.814304697, 1216728.9256139707 THC EPSG:32614 WGS 84 / UTM zone 14N 3349064.8428058745, 620908.0252681801 CapMetro Conversions done with https://epsg.io/transform
  • 8. Database OGR_FID SHAPE route_id routename direction routecolor textcolor routetype routetheme servicenm servicetyp sign_id service_id source sourcedate 1 BLOB 1 North Lamar/South Congress Southbound 004A97 FFFFFF Local NULL Weekday Weekday 153 1-153 Capital Metro 6/3/2022 2 BLOB 324 Georgian/Ohlen Westbound 004A97 FFFFFF Crosstown NULL Sunday Sunday 153 5-153 Capital Metro 6/3/2022 3 BLOB 19 Bull Creek Northbound 004A97 FFFFFF Local NULL Sunday Sunday 153 5-153 Capital Metro 6/3/2022 4 BLOB 243 Wells Branch Westbound 004A97 FFFFFF Feeder NULL Sunday Sunday 153 5-153 Capital Metro 6/3/2022 5 BLOB 6 East 12th Westbound 004A97 FFFFFF Local NULL Saturday Saturday 153 4-153 Capital Metro 6/3/2022 6 BLOB 310 Parker/Wickersham Eastbound 004A97 FFFFFF Crosstown NULL Weekday Weekday 153 1-153 Capital Metro 6/3/2022 7 BLOB 339 Tuscany Westbound 004A97 FFFFFF Crosstown NULL Saturday Saturday 153 4-153 Capital Metro 6/3/2022 8 BLOB 243 Wells Branch Eastbound 004A97 FFFFFF Feeder NULL Weekday Weekday 153 1-153 Capital Metro 6/3/2022 9 BLOB 322 Chicon/Cherrywood Southbound 004A97 FFFFFF Crosstown NULL Sunday Sunday 153 5-153 Capital Metro 6/3/2022 10 BLOB 550 Metro Rail Red Line Northbound E2231A FFFFFF Rail NULL RAIL AFC 2 Other 153 55004-153 Capital Metro 6/3/2022
  • 9. GeoJSON 1. Export from the DB with this SQL: 2. Load the GeoJSON in Leaflet by doing this: SELECT ST_AsGeoJSON(SHAPE), servicenm, servicetyp FROM capmetro_routes WHERE route_id = 550 AND servicenm = '6TRAINMON to THURS'; L.geoJSON(route).addTo(map);
  • 10. GeoJSON Primitives Type Example Point { "type": "Point", "coordinates": [30.0, 10.0] } LineString { "type": "LineString", "coordinates": [ [30.0, 10.0], [10.0, 30.0], [40.0, 40.0] ] } Polygon { "type": "Polygon", "coordinates": [ [[30.0, 10.0], [40.0, 40.0], [20.0, 40.0], [10.0, 20.0], [30.0, 10.0]] ] } { "type": "Polygon", "coordinates": [ [[35.0, 10.0], [45.0, 45.0], [15.0, 40.0], [10.0, 20.0], [35.0, 10.0]], [[20.0, 30.0], [35.0, 35.0], [30.0, 20.0], [20.0, 30.0]] ] }
  • 11. Markers 1. Export from the DB with this SQL: 2. Load the GeoJSON in Leaflet by doing this: SELECT stop_name, CONCAT(latitude, ',', longitude) AS pos FROM capmetro_stops WHERE stop_type = 'Rail Station'; L.marker([30.264843,-97.738448]) .bindPopup('Downtown Station’) .addTo(map);
  • 12. GTFS Realtime • General Transit Feed Specification • Developed by Google in 2006 • Uses Protocol Buffers • Get the download link for "CapMetro Vehicle Positions PB File" from https://www.capmetro.org/metrolabs. Sort by "Recently Updated" • Updated every 15s • composer require google/gtfs-realtime-bindings • Deprecated: As of February 2019, the official google-protobuf Google protoc tool doesn’t support proto2 files. As a result we are deprecating the PHP bindings until official support for proto2 files is implemented in the Google protocol buffer tools.
  • 13. GTFS Realtime: Server Side <?php require_once 'vendor/autoload.php’; use transit_realtimeFeedMessage; $output = []; $data = file_get_contents('https://data.texas.gov/download/eiei-9rpf/application%2Foctet-stream’); $feed = new FeedMessage(); $feed->parse($data); foreach ($feed->getEntityList() as $entity) { if ($entity->vehicle->trip && $entity->vehicle->trip->route_id == 550) { $pos = $entity->vehicle->position; $output[] = [ 'latitude' => $pos->latitude, 'longitude' => $pos->longitude, 'bearing' => $pos->bearing, 'speed' => $pos->speed ]; } } echo json_encode($output);
  • 14. GTFS Realtime: Client Side var realtimeUpdate = function() { fetch('realtime.php’) .then(response => response.json()) .then(data => { data.forEach(train => { var arrow = new L.Icon({ iconUrl: 'arrow-up.svg’, iconSize: [25,28.975], iconAnchor: [13, 0], }); var marker = L.marker( [train.latitude, train.longitude], {icon: arrow, rotationAngle: train.bearing} ) .bindPopup('<strong>Coordinates</strong>: ‘ + train.latitude + ',' + train.longitude + '<br><strong>Bearing</strong>: ' + train.bearing + '<br><strong>Speed</strong>: ' + train.speed) .addTo(map); }); }); } realtimeUpdate(); setInterval(realtimeUpdate, 15000);
  • 15. Putting It Together • arrow-up.svg is from Font Awesome ( ) and was edited with Inkscape • https://github.com/bbecquet/Leaflet.RotatedMar ker is used to rotate the arrow • NYC: https://api.mta.info/ • Los Angeles: https://developer.metro.net/api/ • Chicago: https://www.transitchicago.com/developers/bustracker/ • Houston: https://api-portal.ridemetro.org/ • London: https://api.tfl.gov.uk/ • France: https://prim.iledefrance-mobilites.fr/fr
  • 16. Austin Western Railroad • Download and import shapefile from https://hub.arcgis.com/datasets/fedmaps::north-american-rail- lines-1/explore • Query: SELECT * FROM trains WHERE rrowner1 = 'AWRR'; OGR_FID SHAPE objectid fraarcid frfranode tofranode cntyfips stateab country rrowner1 trkrghts1 subdiv passngr tracks net miles km shape_leng 123293 BLOB 123293 423812 360530 360533 287 TX US AWRR NULL GIDDINGS INDUSTRIA L SPUR NULL 1 I 0.040001 0.064375 0.000666 123294 BLOB 123294 423813 360533 360541 287 TX US AWRR NULL GIDDINGS INDUSTRIA L SPUR NULL 1 I 0.144155 0.231995 0.002261 123386 BLOB 123386 423905 355408 355502 453 TX US AWRR CMRX CENTRAL C 1 M 1.476048 2.375474 0.023111 123413 BLOB 123413 423932 355403 355406 453 TX US AWRR NULL NULL NULL 1 M 0.334526 0.538368 0.004952 123604 BLOB 123604 424123 353382 353376 53 TX US AWRR NULL MAIN LINE NULL 0 O 0.350358 0.563848 0.005183 123705 BLOB 123705 424224 358097 358085 21 TX US AWRR NULL NULL NULL 0 O 0.143777 0.231386 0.002201 123706 BLOB 123706 424225 358629 358637 21 TX US AWRR NULL NULL NULL 0 O 0.073028 0.117527 0.001145 123707 BLOB 123707 424226 358637 358651 21 TX US AWRR NULL NULL NULL 0 O 0.223662 0.35995 0.003704 123708 BLOB 123708 424227 358637 358648 21 TX US AWRR NULL NULL NULL 0 O 0.194351 0.312778 0.003207 124262 BLOB 124262 424783 355418 355416 453 TX US AWRR NULL NULL NULL 1 M 0.204555 0.329199 0.003038
  • 17. GeoJSON Multipart Geometries Type Example MultiPoint { "type": "MultiPoint", "coordinates": [ [10.0, 40.0], [40.0, 30.0], [20.0, 20.0], [30.0, 10.0] ] } MultiLineString { "type": "MultiLineString", "coordinates": [ [[10.0, 10.0], [20.0, 20.0], [10.0, 40.0]], [[40.0, 40.0], [30.0, 30.0], [40.0, 20.0], [30.0, 10.0]] ] } MultiPolygon { "type": "MultiPolygon", "coordinates": [ [ [[30.0, 20.0], [45.0, 40.0], [10.0, 40.0], [30.0, 20.0]] ], [ [[15.0, 5.0], [40.0, 10.0], [10.0, 20.0], [5.0, 10.0], [15.0, 5.0]] ] ] }
  • 18. GeoJSON Multipart Geometries: Part 2 Type Example GeometryCollection { "type": "GeometryCollection", "geometries": [ { "type": "Point", "coordinates": [40.0, 10.0] }, { "type": "LineString", "coordinates": [ [10.0, 10.0], [20.0, 20.0], [10.0, 40.0] ] }, { "type": "Polygon", "coordinates": [ [[40.0, 40.0], [20.0, 45.0], [45.0, 30.0], [40.0, 40.0]] ] } ] }
  • 19. Styles • Colorize layers: • Colorize markers: Colorized markers are from https://github.com/pointhi/leaflet-color-markers L.geoJSON(route, { color: 'red’, }).addTo(map); var redIcon = new L.Icon({ iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-red.png’, shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png’, iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], shadowSize: [41, 41] }); L.marker([30.264843,-97.738448], {icon: redIcon}).bindPopup('Downtown Station').addTo(map);
  • 21. More Styles • Bring train location markers to the front: • Colorize markers: • Add Control Layer: var marker = L.marker( [train.latitude, train.longitude], {icon: arrow, rotationAngle: train.bearing, zIndexOffset: 1000} ); var stations = L.layerGroup(); stations.addLayer(L.marker([30.264843,-97.738448]).bindPopup('Downtown Station’)); stations.addTo(map); layerControl = L.control.layers().addTo(map); layerControl.addOverlay(stations, 'Stations');
  • 23. Counties • Download and import "Counties (and equivalent)" shapefile from https://www.census.gov/cgi- bin/geo/shapefiles/index.php • TIGER: Topologically Integrated Geographic Encoding and Referencing • Query: SELECT * FROM counties WHERE statefp = 48; OGR_FID SHAPE statefp countyfp countyns geoid name namelsad lsad aland awater intptlat intptlon 8BLOB 48327 1383949 48327 Menard Menard County 6 2.34E+09 613559 30.88527 -99.8589 12BLOB 48189 1383880 48189 Hale Hale County 6 2.6E+09 246678 34.06844 -101.823 14BLOB 4811 1383791 48011 Armstrong Armstrong County 6 2.35E+09 12183672 34.96418 -101.357 36BLOB 4857 1383814 48057 Calhoun Calhoun County 6 1.31E+09 1.36E+09 28.44172 -96.5796 39BLOB 4877 1383824 48077 Clay Clay County 6 2.82E+09 72506860 33.7859 -98.2129 56BLOB 48361 1383966 48361 Orange Orange County 6 8.65E+08 1.18E+08 30.12232 -93.8941 64BLOB 48177 1383874 48177 Gonzales Gonzales County 6 2.76E+09 8204086 29.46191 -97.4919 69BLOB 48147 1383859 48147 Fannin Fannin County 6 2.31E+09 20847065 33.59116 -96.105 71BLOB 48265 1383918 48265 Kerr Kerr County 6 2.86E+09 10231764 30.05995 -99.3533 100BLOB 48391 1383981 48391 Refugio Refugio County 6 2E+09 1.24E+08 28.32212 -97.1625
  • 24. Counties: Client Side var exclude = [], counties = L.layerGroup(), temp; layerControl.addOverlay(counties, 'Counties’); getCounties(map.getBounds()); map.on('moveend', function() { getCounties(map.getBounds()); }); function getCounties(bounds) { var ne = bounds.getNorthEast().lat + ',' + bounds.getNorthEast().lng; var sw = bounds.getSouthWest().lat + ',' + bounds.getSouthWest().lng; fetch('counties.php?ne=' + ne + '&sw=' + sw + '&exclude=' + exclude.join(',’)) .then(response => response.json()) .then(data => { data.forEach(county => { counties.addLayer(temp = L.geoJson(county.shape, {fill: false, color: "gray"})); counties.addLayer( L.marker(temp.getBounds().getCenter(), {opacity: 0}) .bindTooltip(county.name, {permanent: true, direction: 'center', className: 'countyName’}) ); exclude.push(county.fips); }); counties.addTo(map); }); }
  • 25. Counties: Server Side <?php $db = new PDO('mysql:dbname=dbname;host=host', 'user', 'pass’); $ne = explode(',', $_GET['ne’]); $sw = explode(',', $_GET['sw’]); $shape = "Polygon(( $sw[0] $sw[1], $ne[0] $sw[1], $ne[0] $ne[1], $sw[0] $ne[1], $sw[0] $sw[1] ))"; $sql_where = ‘’; $exclude = []; if (strlen($_GET['exclude'])) { $exclude = explode(',', $_GET['exclude’]); $sql_in = array_fill(0, count($exclude), '?’); $sql_in = implode(',', $sql_in); $sql_where = "AND countyfp NOT IN ($sql_in)"; } $q = $db->prepare(“ SELECT name, countyfp AS fips, ST_AsGeoJson(SHAPE) AS shape FROM county_shapes WHERE MBRIntersects(ST_GeomFromText(?, 4269), SHAPE) AND statefp = 48 AND ST_Area(ST_GeomFromText(?, 4269)) < 40075210158 $sql_where "); $q->execute(array_merge([$shape, $shape], $exclude)); $result = []; while ($row = $q->fetch(PDO::FETCH_ASSOC)) { $row['shape'] = json_decode($row['shape’]); $result[] = $row; } echo json_encode($result);
  • 29. Polygon Labels var countyNames; function labelCounties() { var bounds = map.getBounds(); if (countyNames) { map.removeLayer(countyNames); } countyNames = L.layerGroup(); for (const [name, layer] of Object.entries(countyLayers)) { var newCoords = [], coords = countyLayers[name].toGeoJSON()['features'][0]['geometry']['coordinates'][0] coords.forEach(function (coord) { coord = L.latLng(coord[1], coord[0]); coord.lat = Math.min(coord.lat, bounds.getNorthWest().lat); coord.lat = Math.max(coord.lat, bounds.getSouthEast().lat); coord.lng = Math.max(coord.lng, bounds.getNorthWest().lng); coord.lng = Math.min(coord.lng, bounds.getSouthEast().lng); newCoords.push(coord); }); var polygon = L.polygon(newCoords, {fill: false, opacity: 0}).addTo(map); marker = L.marker(polygon.getCenter(), {opacity: 0}).bindTooltip(name, {permanent: true, direction: 'center', className: 'countyName’}); countyNames.addLayer(marker); map.removeLayer(polygon); } countyNames.addTo(map); } labelCounties(); map.on('moveend', function() { labelCounties(); });
  • 31. The Narrows: With Counties
  • 32. Labels for Lines • Using https://github.com/3mapslab/Leaflet.streetlabels • Merge LineString’s: • Wrap GeoJSON: $coords = $rows[0]; unset($rows[0]); while (count($rows)) { foreach ($rows as $i => $line) { if ($coords[count($coords) - 1] == $line[0]) { $coords = array_merge($coords, array_slice($line, 1)); unset($rows[$i]); } else if ($coords[0] == $line[count($line) - 1]) { $coords = array_merge(array_slice($line, 0, -1), $coords); unset($rows[$i]); } } } function featureWrapper(geometry, name) { return feature = { type: 'Feature’, properties: {name: name}, geometry: geometry }; } $coords = [ 'type' => 'LineString’, 'coordinates' => $cords ];
  • 33. The Narrows: With Labeled Lines
  • 37. Georeferencing 1 Choose Tool • ArcGIS • QGIS • Georeferencer.com • MapWarper.net 3 Place Image 1. Extract bounding coordinates: 2. Convert to PNG or WebP 3. Place on map: gdalinfo exported.tiff L.imageOverlay( 'exported.webp’, [ // top left [30.2725146, -97.7578187], // bottom right [30.2644510, -97.7496347] ], {opacity: 0.5} ).addTo(map); 2 Basic Technique
  • 39. Merging Overlapping GeoTIFFs • Perform the merge: • "In areas of overlap, the last image will be copied over earlier ones" • Copy nextPage.tif as a new layer over temp.tif and trim away at nextPage.tif • Copy the trimmed layer back to nextPage.tif, delete the old layer, and save as new.tif. GeoTIFF data will be lost. gdal_merge -o temp.tif nextPage.tif prevPage.tif
  • 40. Creating Tile Layers 1 2 3 Copy GeoTIFF data from orig.tif to new.tif listgeo -no_norm orig.tif > orig.geo geotifcp -g orig.geo new.tif temp.tif Merge the TIFFs gdal_merge -o merged.tif master.tif temp.tif Rm master.tif; rm temp.tif; mv merged.tif master.tif Create Tile Layer Gdal2tiles master.tif 4 Use Tile Layer L.tileLayer('https://domain.tld/sanborn/{z}/{x}/{y}.png’, { tms: 1, opacity: 0.7, minZoom: 13, maxZoom: 19 });
  • 42. Tile Layer Caveats "OSM does NOT pre-render every tile. Pre-rendering all tiles would use around 54 TB of storage. As the following table shows, the majority of tiles are never viewed. In fact just 1.79% are viewed. It works out this way because the majority of tiles are at zoom level 18 and actually the majority contain nothing of interest. By following an on-the-fly rendering approach we can avoid rendering these tiles unnecessarily. The tile view count column shows how many tiles have been produced on the OSM Tile server." Source: https://wiki.openstreetmap.org/wiki/Tile_disk_usage
  • 43. Thank You • Slides & Feedback: https://joind.in/talk/44a76 • Questions? terrafrost@php.net