Created
October 19, 2015 14:42
-
-
Save sbinet/2ec0a2e9d4daa8d6455c to your computer and use it in GitHub Desktop.
geo-go jet france
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"encoding/xml" | |
"fmt" | |
"io" | |
"io/ioutil" | |
"log" | |
"math" | |
"math/rand" | |
"os" | |
"os/exec" | |
"strconv" | |
"sync" | |
"time" | |
) | |
type JetAlgorithm func(j1, j2 Jet, deltaR float64) float64 | |
func AntiKt(j1, j2 Jet, deltaR float64) float64 { | |
if 1/j1.Sum.Pop > 1/j2.Sum.Pop { | |
return deltaR / float64(j2.Sum.Pop*j2.Sum.Pop) | |
} | |
return deltaR / float64(j1.Sum.Pop*j1.Sum.Pop) | |
} | |
func Kt(j1, j2 Jet, deltaR float64) float64 { | |
if j1.Sum.Pop > j2.Sum.Pop { | |
return deltaR * float64(j2.Sum.Pop*j2.Sum.Pop) | |
} | |
return deltaR * float64(j1.Sum.Pop*j1.Sum.Pop) | |
} | |
type City struct { | |
ID int `xml:"ville_id"` | |
Name string `xml:"ville_nom_simple"` | |
Pop int `xml:"ville_population_2012"` | |
Lat float64 `xml:"ville_latitude_deg"` | |
Lon float64 `xml:"ville_longitude_deg"` | |
Dep int `xml:"ville_departement"` | |
} | |
type Jet struct { | |
Sum City | |
Constituents []City | |
Option string | |
} | |
func main() { | |
beg := time.Now() | |
defer func() { | |
delta := time.Since(beg) | |
log.Printf("processing took: %v\n", delta) | |
}() | |
var wg sync.WaitGroup | |
fname := "villes_france.xml" | |
if len(os.Args) > 1 { | |
fname = os.Args[1] | |
} | |
cities := loadCities(fname) | |
log.Printf("cities: %8d\n", len(cities)) | |
cities0 := selectCities(cities, 0, 1000) | |
cities1 := selectCities(cities, 1000, 10000) | |
cities2 := selectCities(cities, 10000, 100000) | |
cities3 := selectCities(cities, 100000, 1000000000) | |
log.Printf("cities-0: %8d\n", len(cities0)) | |
log.Printf("cities-1: %8d\n", len(cities1)) | |
log.Printf("cities-2: %8d\n", len(cities2)) | |
log.Printf("cities-3: %8d\n", len(cities3)) | |
jets := make([]Jet, 0, len(cities)) | |
for _, city := range cities { | |
jets = append(jets, | |
Jet{ | |
Sum: city, | |
Constituents: []City{city}, | |
Option: getColor(), | |
}, | |
) | |
} | |
curSize := len(jets) | |
oldSize := 0 | |
ifirst := 0 | |
for curSize > 0 && oldSize != curSize { | |
oldSize = curSize | |
jets, ifirst = findJet(jets, AntiKt, 100, ifirst) | |
curSize = len(jets) | |
log.Printf("still %d cities to process...\n", len(jets)) | |
if curSize%100 == 0 { | |
wg.Add(1) | |
err := dumpBaseMap(jets, &wg) | |
if err != nil { | |
log.Fatalf("error dumping basemap: %v\n", err) | |
} | |
log.Printf("map done\n") | |
} | |
} | |
wg.Wait() | |
} | |
func loadCities(fname string) []City { | |
f, err := os.Open(fname) | |
if err != nil { | |
log.Fatalf("error opening file [%s]: %v\n", fname, err) | |
} | |
defer f.Close() | |
var ( | |
cities []City | |
latmax = 0.0 | |
latmin = 0.0 | |
lonmin = 0.0 | |
lonmax = 0.0 | |
) | |
dec := xml.NewDecoder(f) | |
for { | |
// Read tokens from the XML document in a stream. | |
t, err := dec.Token() | |
if err != nil { | |
if err != io.EOF { | |
log.Fatalf("error decoding token: %v\n", err) | |
} | |
break | |
} | |
if t == nil { | |
break | |
} | |
switch se := t.(type) { | |
case xml.StartElement: | |
if se.Name.Local == "table" { | |
var table Table | |
err = dec.DecodeElement(&table, &se) | |
if err != nil { | |
log.Fatalf("error decoding 'table' element: %v\n", err) | |
} | |
city := newCity(table) | |
if !(city.Name != "" && city.Dep < 100 && city.Pop > 0) { | |
continue | |
} | |
cities = append(cities, city) | |
if city.Lon < lonmin { | |
lonmin = city.Lon | |
} | |
if city.Lon > lonmax { | |
lonmax = city.Lon | |
} | |
if city.Lat < latmin { | |
latmin = city.Lat | |
} | |
if city.Lat > latmax { | |
latmax = city.Lat | |
} | |
} | |
} | |
} | |
log.Printf("cities: %d\n", len(cities)) | |
log.Printf("lon min=%8.3f max=%8.3f\n", lonmin, lonmax) | |
log.Printf("lat min=%8.3f max=%8.3f\n", latmin, latmax) | |
return cities | |
} | |
func selectCities(cities []City, min, max int) []City { | |
var sample []City | |
for _, city := range cities { | |
if min <= city.Pop && city.Pop < max { | |
sample = append(sample, city) | |
} | |
} | |
return sample | |
} | |
type Table struct { | |
Name string `xml:"name,attr"` | |
Cols []Column `xml:"column"` | |
} | |
type Column struct { | |
Name string `xml:"name,attr"` | |
Value string `xml:",chardata"` | |
} | |
func newCity(t Table) City { | |
var err error | |
var city City | |
for _, col := range t.Cols { | |
switch col.Name { | |
case "ville_id": | |
city.ID, err = strconv.Atoi(col.Value) | |
if err != nil { | |
log.Fatalf( | |
"error decoding integer value (ID=%q): %v\n", | |
col.Value, | |
err, | |
) | |
} | |
case "ville_departement": | |
if col.Value == "2A" || col.Value == "2B" { | |
col.Value = "2" | |
} | |
city.Dep, err = strconv.Atoi(col.Value) | |
if err != nil { | |
log.Fatalf( | |
"error decoding integer value (dep=%q): %v\n", | |
col.Value, | |
err, | |
) | |
} | |
case "ville_nom_simple": | |
city.Name = col.Value | |
case "ville_population_2012": | |
city.Pop, err = strconv.Atoi(col.Value) | |
if err != nil { | |
log.Fatalf( | |
"error decoding integer value (population=%q): %v\n", | |
col.Value, | |
err, | |
) | |
} | |
case "ville_latitude_deg": | |
city.Lat, err = strconv.ParseFloat(col.Value, 64) | |
if err != nil { | |
log.Fatalf( | |
"error decoding float value (latitude=%q): %v\n", | |
col.Value, | |
err, | |
) | |
} | |
case "ville_longitude_deg": | |
city.Lon, err = strconv.ParseFloat(col.Value, 64) | |
if err != nil { | |
log.Fatalf( | |
"error decoding float value (longitude=%q): %v\n", | |
col.Value, | |
err, | |
) | |
} | |
} | |
} | |
return city | |
} | |
func findJet(jets []Jet, alg JetAlgorithm, dRMax float64, ifirst int) ([]Jet, int) { | |
i1 := ifirst | |
i1min := -1 | |
i2min := -1 | |
dRMin := 1e9 | |
dRMax2 := dRMax * dRMax | |
tobreak := false | |
for _, jet1 := range jets[ifirst:] { | |
i2 := ifirst | |
for _, jet2 := range jets[ifirst:] { | |
if i2 <= i1 { | |
i2++ | |
continue | |
} | |
lat := jet1.Sum.Lat - jet2.Sum.Lat | |
lon := (jet1.Sum.Lon - jet2.Sum.Lon) * math.Cos((jet1.Sum.Lat+jet2.Sum.Lat)*0.5) | |
dR := lat*lat + lon*lon | |
dR *= 10000.0 / 90.0 | |
dR *= 10000.0 / 90.0 // in km | |
if dR > dRMax2 { | |
continue | |
} | |
dRCut := alg(jet1, jet2, dR) | |
if dRCut < dRMin { | |
i1min = i1 | |
i2min = i2 | |
dRMin = dRCut | |
if dR == 0.0 { | |
tobreak = true | |
break | |
} | |
} | |
i2++ | |
} | |
if tobreak { | |
tobreak = false | |
break | |
} | |
i1++ | |
} | |
iname := 0 | |
ikeep := 0 | |
iremove := 0 | |
if i1min >= 0 { | |
j1 := jets[i1min] | |
j2 := jets[i2min] | |
log.Printf( | |
"merging %q (idx=%d) and %q (idx=%d) -- dR-min=%v...\n", | |
j1.Sum.Name, i1min, | |
j2.Sum.Name, i2min, | |
dRMin, | |
) | |
// take the name of the biggest city | |
if j1.Sum.Pop > j2.Sum.Pop { | |
iname = i1min | |
} else { | |
iname = i2min | |
} | |
// take the place of the first city | |
if i2min > i1min { | |
ikeep = i1min | |
iremove = i2min | |
} else { | |
ikeep = i2min | |
iremove = i1min | |
} | |
var ( | |
newElement Jet | |
badElement Jet | |
) | |
switch iname { | |
case i1min: | |
newElement = jets[i1min] | |
badElement = jets[i2min] | |
case i2min: | |
newElement = jets[i2min] | |
badElement = jets[i1min] | |
} | |
newElement.Constituents = append( | |
newElement.Constituents, | |
badElement.Constituents..., | |
) | |
newElement.Sum.Pop += badElement.Sum.Pop | |
newElement.Sum.Lat = (newElement.Sum.Lat*float64(newElement.Sum.Pop) + badElement.Sum.Lat*float64(badElement.Sum.Pop)) / float64(newElement.Sum.Pop+badElement.Sum.Pop) | |
newElement.Sum.Lon = (newElement.Sum.Lon*float64(newElement.Sum.Pop) + badElement.Sum.Lon*float64(badElement.Sum.Pop)) / float64(newElement.Sum.Pop+badElement.Sum.Pop) | |
jets[ikeep] = newElement | |
jets = append(jets[:iremove], jets[iremove+1:]...) | |
} | |
if dRMin == 0.0 { | |
return jets, ikeep | |
} | |
return jets, ifirst | |
} | |
func dumpBaseMap(jets []Jet, wg *sync.WaitGroup) error { | |
var err error | |
fname := fmt.Sprintf("data_fig_%06d_cities.txt", len(jets)) | |
out, err := os.Create(fname) | |
if err != nil { | |
return err | |
} | |
defer out.Close() | |
for _, jet := range jets { | |
if jet.Sum.Pop < 10000 { | |
continue | |
} | |
for _, city := range jet.Constituents { | |
fmt.Fprintf(out, "%f;%f;%s\n", city.Lon, city.Lat, jet.Option) | |
} | |
} | |
err = out.Close() | |
if err != nil { | |
return err | |
} | |
py := fmt.Sprintf(`#!/usr/bin/env python2 | |
import sys | |
import numpy as np | |
from mpl_toolkits.basemap import Basemap | |
import matplotlib.pyplot as plt | |
def main(fname): | |
m = Basemap(projection='merc',llcrnrlat=41,urcrnrlat=52, | |
llcrnrlon=-6,urcrnrlon=10,lat_ts=15,resolution='c') | |
#m.drawmapboundary(fill_color='aqua') | |
m.bluemarble() | |
#m.drawcoastlines() | |
#m.fillcontinents(color='coral',lake_color='aqua') | |
m.drawparallels(np.arange(10,90,20)) | |
m.drawmeridians(np.arange(-180,180,30)) | |
m.drawcountries() | |
for line in open(fname).readlines(): | |
values = line.strip().split(";") | |
lon = float(values[0]) | |
lat = float(values[1]) | |
drawOpt = values[2] | |
m.plot(lon, lat, drawOpt, latlon=True) | |
pass | |
plt.title("%[1]d cities") | |
plt.savefig("fig_go_%[1]d.png") | |
return | |
if __name__ == "__main__": | |
main(%[2]q) | |
`, | |
len(jets), | |
fname, | |
) | |
go func(py, fname string, wg *sync.WaitGroup) { | |
err := ioutil.WriteFile(fname+".py", []byte(py), 0644) | |
if err != nil { | |
log.Fatalf("error creating python script for [%s]: %v\n", | |
fname, | |
err, | |
) | |
} | |
defer os.Remove(fname + ".py") | |
cmd := exec.Command( | |
"python2", | |
fname+".py", | |
) | |
cmd.Stdin = os.Stdin | |
cmd.Stdout = os.Stdout | |
cmd.Stderr = os.Stderr | |
log.Printf(":: processing [%s]...\n", fname) | |
err = cmd.Run() | |
wg.Done() | |
log.Printf(":: removing [%s]...\n", fname) | |
if err != nil { | |
errRm := os.Remove(fname) | |
if errRm != nil { | |
log.Printf("error removing data file [%s]: %v\n", fname, errRm) | |
} | |
log.Fatalf("error processing [%s]: %v\n", fname, err) | |
} | |
errRm := os.Remove(fname) | |
if errRm != nil { | |
log.Printf("error removing data file [%s]: %v\n", fname, errRm) | |
} | |
}(py, fname, wg) | |
return err | |
} | |
var colors = []string{ | |
"og", "or", "ob", "oy", | |
"+g", "+r", "+b", "+y", | |
"*g", "*r", "*b", "*y", | |
"xg", "xr", "xb", "xy", | |
} | |
func getColor() string { | |
idx := rand.Intn(len(colors)) | |
return colors[idx] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment