├── .gitignore ├── README.md ├── earthdown.go └── northern-territory-australia-2260.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Example Image](northern-territory-australia-2260.jpg) 2 | 3 | ## Earthdown 4 | 5 | Simple CLI to download images from [Google Earth View](https://earthview.withgoogle.com/). 6 | 7 | ### Installation 8 | 9 | ``` 10 | $ go get -u github.com/s3ththompson/earthdown 11 | ``` 12 | 13 | ### Usage 14 | 15 | ``` 16 | earthdown [options...] EARTH_VIEW_URL 17 | Options: 18 | -o name of output file 19 | ``` 20 | 21 | ### Example 22 | 23 | ``` 24 | $ earthdown https://g.co/ev/2260 25 | Northern Territory, Australia – Earth View from Google 26 | Lat: -11.992309, Lng: 131.807527, ©2014 CNES / Astrium, Cnes/Spot Image, DigitalGlobe, Landsat, Sinclair Knight Merz, Sinclair Knight Merz & Fugro 27 | Downloaded https://www.gstatic.com/prettyearth/assets/full/2260.jpg to northern-territory-australia-2260.jpg (1 file, 261.3 kB) 28 | ``` -------------------------------------------------------------------------------- /earthdown.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "net/http" 10 | "net/url" 11 | "os" 12 | "path" 13 | 14 | "github.com/wzshiming/ctc" 15 | ) 16 | 17 | type EarthViewItem struct { 18 | Id string `json:"id"` 19 | Slug string `json:"slug"` 20 | Title string `json:"title"` 21 | Region string `json:"region"` 22 | Country string `json:"country"` 23 | Lat float64 `json:"lat"` 24 | Lng float64 `json:"lng"` 25 | PhotoURL string `json:"photoUrl"` 26 | Attribution string `json:"attribution"` 27 | MapsLink string `json:"mapsLink"` 28 | EarthLink string `json:"earthLink"` 29 | } 30 | 31 | const Usage = `Usage: earth [options...] EARTH_VIEW_URL 32 | Options: 33 | -o name of output file 34 | ` 35 | 36 | var ( 37 | o = flag.String("o", "", "") 38 | ) 39 | 40 | func main() { 41 | flag.Usage = func() { 42 | fmt.Fprint(os.Stderr, Usage) 43 | } 44 | 45 | flag.Parse() 46 | 47 | earthURL := "" 48 | if flag.NArg() > 0 { 49 | earthURL = flag.Args()[0] 50 | } 51 | output := *o 52 | 53 | if earthURL == "" { 54 | fmt.Fprint(os.Stderr, Usage) 55 | return 56 | } 57 | 58 | resp, err := http.Get(earthURL) 59 | if err != nil { 60 | printError("error requesting url, ", earthURL) 61 | return 62 | } 63 | 64 | expandedURL := resp.Request.URL.String() 65 | apiURL, err := url.ParseRequestURI(expandedURL) 66 | if err != nil || apiURL.Host != "earthview.withgoogle.com" { 67 | printError("error resolving url, expecting host earthview.withgoogle.com") 68 | return 69 | } 70 | 71 | apiURL.Path = path.Join("_api", apiURL.Path+".json") 72 | resp, err = http.Get(apiURL.String()) 73 | if err != nil { 74 | printError("error requesting api, ", apiURL.String()) 75 | return 76 | } 77 | defer resp.Body.Close() 78 | 79 | body, err := ioutil.ReadAll(resp.Body) 80 | if err != nil { 81 | printError("error reading api request body") 82 | return 83 | } 84 | 85 | var item EarthViewItem 86 | err = json.Unmarshal(body, &item) 87 | if err != nil { 88 | printError("error decoding json") 89 | return 90 | } 91 | fmt.Print(ctc.ForegroundGreen, item.Region, ", ", item.Country, ctc.Reset, "\n") 92 | fmt.Print("Lat: ", ctc.ForegroundBlue, item.Lat, "°", ctc.Reset, ", Lng: ", ctc.ForegroundBlue, item.Lng, "°", ctc.Reset, ", ", item.Attribution, "\n") 93 | fmt.Print("Link: ", ctc.ForegroundBlue, item.EarthLink, ctc.Reset, "\n") 94 | 95 | if output == "" { 96 | output = item.Slug + ".jpg" 97 | } 98 | out, err := os.Create(output) 99 | if err != nil { 100 | printError("error writing output, ", output) 101 | return 102 | } 103 | defer out.Close() 104 | 105 | resp, err = http.Get(item.PhotoURL) 106 | if err != nil { 107 | printError("error downloading file, ", item.PhotoURL) 108 | return 109 | } 110 | defer resp.Body.Close() 111 | 112 | n, err := io.Copy(out, resp.Body) 113 | if err != nil { 114 | printError("error downloading file, ", item.PhotoURL) 115 | return 116 | } 117 | fmt.Print("Downloaded ", ctc.ForegroundBlue, item.PhotoURL, ctc.Reset, " to ", ctc.ForegroundBlue, output, ctc.ForegroundYellow, " (1 file, ", byteCountDecimal(n), ")", ctc.Reset, "\n") 118 | } 119 | 120 | func printError(a ...interface{}) { 121 | var message []interface{} 122 | message = append(message, ctc.ForegroundRed) 123 | message = append(message, a...) 124 | message = append(message, ctc.Reset, "\n") 125 | fmt.Fprint(os.Stderr, message...) 126 | } 127 | 128 | // programming.guide/go/formatting-byte-size-to-human-readable-format.html 129 | func byteCountDecimal(b int64) string { 130 | const unit = 1000 131 | if b < unit { 132 | return fmt.Sprintf("%d B", b) 133 | } 134 | div, exp := int64(unit), 0 135 | for n := b / unit; n >= unit; n /= unit { 136 | div *= unit 137 | exp++ 138 | } 139 | return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "kMGTPE"[exp]) 140 | } 141 | -------------------------------------------------------------------------------- /northern-territory-australia-2260.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s3ththompson/earthdown/36e58511de334013980db082f6a56f2998161c7d/northern-territory-australia-2260.jpg --------------------------------------------------------------------------------