[
  {
    "path": ".gitignore",
    "content": ".idea/\ndump.rdb\n"
  },
  {
    "path": "README.md",
    "content": "## Loc2Country\n\nLocation coordinates (lat/lon) to ISO alpha-3 country code. Responds in microseconds.\n\n## Manual\n\nInput format: latitude, longitude\n\nOutput format: 3-letter-ISO-country-code, time-taken-to-respond-in-nanos\n\n## HowTo\n\n1. Run start.sh\n2. This will start a TCP server (localhost:3333) by default.\n3. Connect to the server by using telnet. (eg: \"telnet localhost 3333\")\n4. Input lat and lon seperated by comma, returns 3 letter country code and time taken to respond in nanoseconds.\n\n## Compiling\n\nTo compile, run:\n\n``` bash\ngo build src/server.go\n```\n\nTo compile for a Linux machine from Mac, run (with correct architecture):\n\n``` bash\nenv GOOS=linux GOARCH=amd64 go build src/server.go\n```\n\n## Testing\n\nTo test, run:\n\n```\ngo test\n```\n\n\n## Example\n\nStarting the server:\n\n``` bash\n$ sh start.sh \n2016/08/18 23:30:07 Creating server with address localhost:3333\n2016/08/18 23:30:07 Loading data..\n2016/08/18 23:30:13 Loading complete.\n2016/08/18 23:30:13 Total Entries: 5235316\n2016/08/18 23:30:13 Boot time: 5 seconds\n```\n\n``` bash\n$ telnet 127.0.0.1 3333\nTrying 127.0.0.1...\nConnected to localhost.\nEscape character is '^]'.\n12,77\nIND,17176\n```\n\n## Data\n\nThe 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). \n\n\n## Contributors\n\nSooraj B - [@soorajb](http://github.com/soorajb)\n\nAshwin Nair - [@ashwin711](http://github.com/ashwin711)\n\nHarikrishnan Shaji - [@har777](http://github.com/har777)\n\n## License\n\nMIT License\n"
  },
  {
    "path": "bin/.keep",
    "content": ""
  },
  {
    "path": "build_linux.sh",
    "content": "env GOOS=linux GOARCH=amd64 go build src/server.go\n"
  },
  {
    "path": "data/.keep",
    "content": ""
  },
  {
    "path": "src/server.go",
    "content": "package main\n\nimport (\n  \"bufio\"\n  \"compress/gzip\"\n  \"flag\"\n  \"log\"\n  \"os\"\n  \"strconv\"\n  \"strings\"\n  \"time\"\n\n  \"github.com/firstrow/tcp_server\"\n  \"github.com/mmcloughlin/geohash\"\n)\n\nvar serverHost = flag.String(\"host\", \"localhost\", \"Hostname to bind to. eg: 192.168.1.10, default:localhost\")\nvar serverPort = flag.String(\"port\", \"3333\", \"Port eg: 8080, default: 3333\")\nvar dataPath = flag.String(\"dataPath\", \"./data/master.csv.gz\", \"Path to data file. eg: ./data/file.gz, default: ./data/master.csv.gz\")\n\nfunc main() {\n  flag.Parse()\n  connString := *serverHost + \":\" + *serverPort\n  dataFilePath := *dataPath\n\n  server := tcp_server.New(connString)\n  geohashToCountryMapping := getGeohashToCountryMapping(dataFilePath)\n\n  server.OnNewMessage(func(c *tcp_server.Client, message string) {\n    startNano := time.Now().UnixNano()\n    reply := messageHandler(message, geohashToCountryMapping)\n    c.Send(reply + \"\\n\")\n    log.Println(\"Response time: \" + strconv.FormatInt((time.Now().UnixNano()-startNano), 10))\n  })\n  server.Listen()\n}\n\nfunc messageHandler(message string, geohashToCountryMapping map[string]string) string {\n  message = strings.TrimSpace(message)\n  response := \"\"\n  startNano := time.Now().UnixNano()\n  if message != \"\" {\n    lat, lon := parseLatLonFromMessage(message)\n    geohash6 := generateGeohash(lat, lon)\n    country := getCountryFromGeohashToCountryMapping(geohash6, geohashToCountryMapping)\n    timeTaken := time.Now().UnixNano() - startNano\n    response = country + \", \" + strconv.FormatInt(timeTaken, 10)\n  }\n\n  log.Println(message + \" => \" + response)\n  return response\n}\n\nfunc getCountryFromGeohashToCountryMapping(geohash6 string, geohashToCountryMapping map[string]string) string {\n  country := \"\"\n  for geohashLength := 6; geohashLength > 1; geohashLength-- {\n    country = geohashToCountryMapping[geohash6[0:geohashLength]]\n    if country != \"\" {\n      break\n    }\n  }\n  return country\n}\n\nfunc parseLatLonFromMessage(message string) (float64, float64) {\n  coords := strings.Split(message, \",\")\n  lat, lon := 0.0, 0.0\n  if len(coords) == 2 {\n    lat, _ = strconv.ParseFloat(strings.TrimSpace(coords[0]), 64)\n    lon, _ = strconv.ParseFloat(strings.TrimSpace(coords[1]), 64)\n  }\n  return lat, lon\n}\n\nfunc generateGeohash(lat float64, lon float64) string {\n  return geohash.EncodeWithPrecision(lat, lon, 6)\n}\n\nfunc getGeohashToCountryMapping(filePath string) map[string]string {\n  geohashToCountryMapping := make(map[string]string)\n  startNano := time.Now().UnixNano()\n  log.Println(\"Loading data.\")\n  file, err := os.Open(filePath)\n  defer file.Close()\n  checkError(err)\n\n  gzipReader, err := gzip.NewReader(file)\n  defer gzipReader.Close()\n  checkError(err)\n\n  scanner := bufio.NewScanner(gzipReader)\n  for scanner.Scan() {\n    line := strings.Split(scanner.Text(), \",\")\n    geohashToCountryMapping[line[0]] = line[1]\n  }\n\n  checkError(scanner.Err())\n  \n  log.Println(\"Loading complete.\")\n  log.Println(\"Total Entries: \" + strconv.Itoa(len(geohashToCountryMapping)))\n  timeTaken := (time.Now().UnixNano() - startNano) / 1000000000\n  log.Println(\"Boot time: \" + strconv.FormatInt(timeTaken, 10) + \" seconds\")\n  return geohashToCountryMapping\n}\n\nfunc checkError(e error) {\n  if e != nil {\n    log.Println(e)\n  }\n}\n"
  },
  {
    "path": "src/server_test.go",
    "content": "package main\n\nimport (\n  \"testing\"\n\n  \"github.com/stretchr/testify/assert\"\n)\n\nfunc TestGenerateGeohash(t *testing.T) {\n  lat := 12.941084\n  lon := 77.6099103\n  expectedGeohash := \"tdr1w5\"\n  actualGeohash := generateGeohash(lat, lon)\n  assert.Equal(t, expectedGeohash, actualGeohash, \"Generated geohash doesnt match with expected geohash.\")\n}\n\nfunc TestParseLatLonFromMessage1(t *testing.T) {\n  message := \"12.941084,77.6099103\"\n  expectedLat, expectedLon := 12.941084, 77.6099103\n  actualLat, actualLon := parseLatLonFromMessage(message)\n  assert.Equal(t, expectedLat, actualLat, \"Generated latitude doesnt match with expected latitude.\")\n  assert.Equal(t, expectedLon, actualLon, \"Generated longitude doesnt match with expected longitude.\")\n}\n\nfunc TestParseLatLonFromMessage2(t *testing.T) {\n  message := \"40.744630, -73.981481\"\n  expectedLat, expectedLon := 40.744630, -73.981481\n  actualLat, actualLon := parseLatLonFromMessage(message)\n  assert.Equal(t, expectedLat, actualLat, \"Generated latitude doesnt match with expected latitude.\")\n  assert.Equal(t, expectedLon, actualLon, \"Generated longitude doesnt match with expected longitude.\")\n}\n\nfunc TestGetCountryFromGeohashToCountryMapping(t *testing.T) {\n  geohash6FromCountryAUS, expectedCountryForAUSGeohash := \"qsxtqw\", \"AUS\"\n  geohash6FromCountryJPN, expectedCountryForJPNGeohash := \"xn7735\", \"JPN\"\n  geohash6FromOcean, expectedCountryForOceanGeohash := \"mxty6f\", \"\"\n\n  geohashToCountryMapping := make(map[string]string)\n  geohashToCountryMapping[\"qsxtqw\"] = \"AUS\"\n  geohashToCountryMapping[\"xn7\"] = \"JPN\"\n\n  actualCountryForAUSGeohash := getCountryFromGeohashToCountryMapping(geohash6FromCountryAUS, geohashToCountryMapping)\n  actualCountryForJPNGeohash := getCountryFromGeohashToCountryMapping(geohash6FromCountryJPN, geohashToCountryMapping)\n  actualCountryForOceanGeohash := getCountryFromGeohashToCountryMapping(geohash6FromOcean, geohashToCountryMapping)\n\n  assert.Equal(t, expectedCountryForAUSGeohash, actualCountryForAUSGeohash, \"Generated country doesnt match with expected country.\")\n  assert.Equal(t, expectedCountryForJPNGeohash, actualCountryForJPNGeohash, \"Generated country doesnt match with expected country.\")\n  assert.Equal(t, expectedCountryForOceanGeohash, actualCountryForOceanGeohash, \"Generated country doesnt match with expected country.\")\n}\n\nfunc TestGetGeohashToCountryMapping(t *testing.T) {\n  expectedGeohashToCountryMapping := make(map[string]string)\n  expectedGeohashToCountryMapping[\"d6nq9t\"] = \"ABW\"\n  expectedGeohashToCountryMapping[\"wh3x0u\"] = \"IND\"\n\n  actualGeohashToCountryMapping := getGeohashToCountryMapping(\"dummy_test_data.csv.gz\")\n\n  assert.Equal(t, expectedGeohashToCountryMapping, actualGeohashToCountryMapping, \"Generated geohashToCountryMapping doesnt match with expected geohashToCountryMapping.\")\n}\n"
  },
  {
    "path": "start.sh",
    "content": "nohup ./bin/server -p 3333 > /dev/null &\n"
  }
]