Repository: soorajb/loc2country Branch: master Commit: 0e5b32e132fb Files: 10 Total size: 7.6 KB Directory structure: gitextract_vsbryt61/ ├── .gitignore ├── README.md ├── bin/ │ ├── .keep │ ├── server_amd64 │ └── server_darwin ├── build_linux.sh ├── data/ │ └── .keep ├── src/ │ ├── server.go │ └── server_test.go └── start.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .idea/ dump.rdb ================================================ FILE: README.md ================================================ ## Loc2Country Location coordinates (lat/lon) to ISO alpha-3 country code. Responds in microseconds. ## Manual Input format: latitude, longitude Output format: 3-letter-ISO-country-code, time-taken-to-respond-in-nanos ## HowTo 1. Run start.sh 2. This will start a TCP server (localhost:3333) by default. 3. Connect to the server by using telnet. (eg: "telnet localhost 3333") 4. Input lat and lon seperated by comma, returns 3 letter country code and time taken to respond in nanoseconds. ## Compiling To compile, run: ``` bash go build src/server.go ``` To compile for a Linux machine from Mac, run (with correct architecture): ``` bash env GOOS=linux GOARCH=amd64 go build src/server.go ``` ## Testing To test, run: ``` go test ``` ## Example Starting the server: ``` bash $ sh start.sh 2016/08/18 23:30:07 Creating server with address localhost:3333 2016/08/18 23:30:07 Loading data.. 2016/08/18 23:30:13 Loading complete. 2016/08/18 23:30:13 Total Entries: 5235316 2016/08/18 23:30:13 Boot time: 5 seconds ``` ``` bash $ telnet 127.0.0.1 3333 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. 12,77 IND,17176 ``` ## Data The world boundaries were generated using QGIS, converted to a set of ~350 million geohashes at precision level 6 and then reduced (compressed) to a set of ~5 million geohashes using [georaptor](https://github.com/ashwin711/georaptor). ## Contributors Sooraj B - [@soorajb](http://github.com/soorajb) Ashwin Nair - [@ashwin711](http://github.com/ashwin711) Harikrishnan Shaji - [@har777](http://github.com/har777) ## License MIT License ================================================ FILE: bin/.keep ================================================ ================================================ FILE: build_linux.sh ================================================ env GOOS=linux GOARCH=amd64 go build src/server.go ================================================ FILE: data/.keep ================================================ ================================================ FILE: src/server.go ================================================ package main import ( "bufio" "compress/gzip" "flag" "log" "os" "strconv" "strings" "time" "github.com/firstrow/tcp_server" "github.com/mmcloughlin/geohash" ) var serverHost = flag.String("host", "localhost", "Hostname to bind to. eg: 192.168.1.10, default:localhost") var serverPort = flag.String("port", "3333", "Port eg: 8080, default: 3333") var dataPath = flag.String("dataPath", "./data/master.csv.gz", "Path to data file. eg: ./data/file.gz, default: ./data/master.csv.gz") func main() { flag.Parse() connString := *serverHost + ":" + *serverPort dataFilePath := *dataPath server := tcp_server.New(connString) geohashToCountryMapping := getGeohashToCountryMapping(dataFilePath) server.OnNewMessage(func(c *tcp_server.Client, message string) { startNano := time.Now().UnixNano() reply := messageHandler(message, geohashToCountryMapping) c.Send(reply + "\n") log.Println("Response time: " + strconv.FormatInt((time.Now().UnixNano()-startNano), 10)) }) server.Listen() } func messageHandler(message string, geohashToCountryMapping map[string]string) string { message = strings.TrimSpace(message) response := "" startNano := time.Now().UnixNano() if message != "" { lat, lon := parseLatLonFromMessage(message) geohash6 := generateGeohash(lat, lon) country := getCountryFromGeohashToCountryMapping(geohash6, geohashToCountryMapping) timeTaken := time.Now().UnixNano() - startNano response = country + ", " + strconv.FormatInt(timeTaken, 10) } log.Println(message + " => " + response) return response } func getCountryFromGeohashToCountryMapping(geohash6 string, geohashToCountryMapping map[string]string) string { country := "" for geohashLength := 6; geohashLength > 1; geohashLength-- { country = geohashToCountryMapping[geohash6[0:geohashLength]] if country != "" { break } } return country } func parseLatLonFromMessage(message string) (float64, float64) { coords := strings.Split(message, ",") lat, lon := 0.0, 0.0 if len(coords) == 2 { lat, _ = strconv.ParseFloat(strings.TrimSpace(coords[0]), 64) lon, _ = strconv.ParseFloat(strings.TrimSpace(coords[1]), 64) } return lat, lon } func generateGeohash(lat float64, lon float64) string { return geohash.EncodeWithPrecision(lat, lon, 6) } func getGeohashToCountryMapping(filePath string) map[string]string { geohashToCountryMapping := make(map[string]string) startNano := time.Now().UnixNano() log.Println("Loading data.") file, err := os.Open(filePath) defer file.Close() checkError(err) gzipReader, err := gzip.NewReader(file) defer gzipReader.Close() checkError(err) scanner := bufio.NewScanner(gzipReader) for scanner.Scan() { line := strings.Split(scanner.Text(), ",") geohashToCountryMapping[line[0]] = line[1] } checkError(scanner.Err()) log.Println("Loading complete.") log.Println("Total Entries: " + strconv.Itoa(len(geohashToCountryMapping))) timeTaken := (time.Now().UnixNano() - startNano) / 1000000000 log.Println("Boot time: " + strconv.FormatInt(timeTaken, 10) + " seconds") return geohashToCountryMapping } func checkError(e error) { if e != nil { log.Println(e) } } ================================================ FILE: src/server_test.go ================================================ package main import ( "testing" "github.com/stretchr/testify/assert" ) func TestGenerateGeohash(t *testing.T) { lat := 12.941084 lon := 77.6099103 expectedGeohash := "tdr1w5" actualGeohash := generateGeohash(lat, lon) assert.Equal(t, expectedGeohash, actualGeohash, "Generated geohash doesnt match with expected geohash.") } func TestParseLatLonFromMessage1(t *testing.T) { message := "12.941084,77.6099103" expectedLat, expectedLon := 12.941084, 77.6099103 actualLat, actualLon := parseLatLonFromMessage(message) assert.Equal(t, expectedLat, actualLat, "Generated latitude doesnt match with expected latitude.") assert.Equal(t, expectedLon, actualLon, "Generated longitude doesnt match with expected longitude.") } func TestParseLatLonFromMessage2(t *testing.T) { message := "40.744630, -73.981481" expectedLat, expectedLon := 40.744630, -73.981481 actualLat, actualLon := parseLatLonFromMessage(message) assert.Equal(t, expectedLat, actualLat, "Generated latitude doesnt match with expected latitude.") assert.Equal(t, expectedLon, actualLon, "Generated longitude doesnt match with expected longitude.") } func TestGetCountryFromGeohashToCountryMapping(t *testing.T) { geohash6FromCountryAUS, expectedCountryForAUSGeohash := "qsxtqw", "AUS" geohash6FromCountryJPN, expectedCountryForJPNGeohash := "xn7735", "JPN" geohash6FromOcean, expectedCountryForOceanGeohash := "mxty6f", "" geohashToCountryMapping := make(map[string]string) geohashToCountryMapping["qsxtqw"] = "AUS" geohashToCountryMapping["xn7"] = "JPN" actualCountryForAUSGeohash := getCountryFromGeohashToCountryMapping(geohash6FromCountryAUS, geohashToCountryMapping) actualCountryForJPNGeohash := getCountryFromGeohashToCountryMapping(geohash6FromCountryJPN, geohashToCountryMapping) actualCountryForOceanGeohash := getCountryFromGeohashToCountryMapping(geohash6FromOcean, geohashToCountryMapping) assert.Equal(t, expectedCountryForAUSGeohash, actualCountryForAUSGeohash, "Generated country doesnt match with expected country.") assert.Equal(t, expectedCountryForJPNGeohash, actualCountryForJPNGeohash, "Generated country doesnt match with expected country.") assert.Equal(t, expectedCountryForOceanGeohash, actualCountryForOceanGeohash, "Generated country doesnt match with expected country.") } func TestGetGeohashToCountryMapping(t *testing.T) { expectedGeohashToCountryMapping := make(map[string]string) expectedGeohashToCountryMapping["d6nq9t"] = "ABW" expectedGeohashToCountryMapping["wh3x0u"] = "IND" actualGeohashToCountryMapping := getGeohashToCountryMapping("dummy_test_data.csv.gz") assert.Equal(t, expectedGeohashToCountryMapping, actualGeohashToCountryMapping, "Generated geohashToCountryMapping doesnt match with expected geohashToCountryMapping.") } ================================================ FILE: start.sh ================================================ nohup ./bin/server -p 3333 > /dev/null &