// just quake regions at the moment. func regions(w http.ResponseWriter, r *http.Request) { if err := regionsD.CheckParams(r.URL.Query()); err != nil { web.BadRequest(w, r, err.Error()) return } if r.URL.Query().Get("type") != "quake" { web.BadRequest(w, r, "type must be quake.") return } var d string err := db.QueryRow(`SELECT row_to_json(fc) FROM ( SELECT 'FeatureCollection' as type, array_to_json(array_agg(f)) as features FROM (SELECT 'Feature' as type, ST_AsGeoJSON(q.geom)::json as geometry, row_to_json((SELECT l FROM ( SELECT regionname as "regionID", title, groupname as group ) as l )) as properties FROM qrt.region as q where groupname in ('region', 'north', 'south')) as f ) as fc`).Scan(&d) if err != nil { web.ServiceUnavailable(w, r, err) return } w.Header().Set("Surrogate-Control", web.MaxAge86400) b := []byte(d) web.Ok(w, r, &b) }
func intensityMeasuredLatest(w http.ResponseWriter, r *http.Request) { if err := intensityMeasuredLatestD.CheckParams(r.URL.Query()); err != nil { web.BadRequest(w, r, err.Error()) return } if r.URL.Query().Get("type") != "measured" { web.BadRequest(w, r, "type must be measured.") return } var d string err := db.QueryRow( `SELECT row_to_json(fc) FROM ( SELECT 'FeatureCollection' as type, COALESCE(array_to_json(array_agg(f)), '[]') as features FROM (SELECT 'Feature' as type, ST_AsGeoJSON(s.location)::json as geometry, row_to_json(( select l from ( select mmi ) as l )) as properties from (select location, mmi FROM impact.intensity_measured) as s ) As f ) as fc`).Scan(&d) if err != nil { web.ServiceUnavailable(w, r, err) return } b := []byte(d) web.Ok(w, r, &b) }
/* ymin, ymax = 0 - not set ymin = ymin and != 0 - single range value ymin != ymax - fixed range */ func getYRange(w http.ResponseWriter, r *http.Request) (ymin, ymax float64, ok bool) { var err error yr := r.URL.Query().Get("yrange") switch { case yr == "": ok = true case strings.Contains(yr, `,`): y := strings.Split(yr, `,`) if len(y) != 2 { web.BadRequest(w, r, "invalid yrange query param.") return } ymin, err = strconv.ParseFloat(y[0], 64) if err != nil { web.BadRequest(w, r, "invalid yrange query param.") return } ymax, err = strconv.ParseFloat(y[1], 64) if err != nil { web.BadRequest(w, r, "invalid yrange query param.") return } default: ymin, err = strconv.ParseFloat(yr, 64) if err != nil || ymin <= 0 { web.BadRequest(w, r, "invalid yrange query param.") return } ymax = ymin } ok = true return }
func quake(w http.ResponseWriter, r *http.Request) { if len(r.URL.Query()) != 0 { web.BadRequest(w, r, "incorrect number of query parameters.") return } publicID := r.URL.Path[quakeLen:] // TODO bother with this? if !publicIDRe.MatchString(publicID) { web.BadRequest(w, r, "invalid publicID: "+publicID) return } var d string // Check that the publicid exists in the DB. This is needed as the handle method will return empty // JSON for an invalid publicID. err := db.QueryRow("select publicid FROM qrt.quake_materialized where publicid = $1", publicID).Scan(&d) if err == sql.ErrNoRows { web.NotFound(w, r, "invalid publicID: "+publicID) return } if err != nil { web.ServiceUnavailable(w, r, err) return } err = db.QueryRow( `SELECT row_to_json(fc) FROM ( SELECT 'FeatureCollection' as type, array_to_json(array_agg(f)) as features FROM (SELECT 'Feature' as type, ST_AsGeoJSON(q.origin_geom)::json as geometry, row_to_json((SELECT l FROM ( SELECT publicid AS "publicID", to_char(origintime, 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"') as "time", depth, magnitude, type, agency, locality, qrt.mmi_to_intensity(maxmmi) as intensity, qrt.mmi_to_intensity(mmi_newzealand) as "regionIntensity", qrt.quake_quality(status, usedphasecount, magnitudestationcount) as quality, to_char(updatetime, 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"') as "modificationTime" ) as l )) as properties FROM qrt.quake_materialized as q where publicid = $1 ) As f ) as fc`, publicID).Scan(&d) if err != nil { web.ServiceUnavailable(w, r, err) return } b := []byte(d) web.Ok(w, r, &b) }
/* returns a zero length list if no sites are found. */ func getSites(w http.ResponseWriter, r *http.Request) ([]siteQ, bool) { var sites = make([]siteQ, 0) for _, ns := range strings.Split(r.URL.Query().Get("sites"), ",") { nss := strings.Split(ns, ".") if len(nss) != 2 { web.BadRequest(w, r, "invalid sites query.") return sites, false } sites = append(sites, siteQ{networkID: nss[0], siteID: nss[1]}) } for _, s := range sites { err := db.QueryRow("select name FROM fits.site join fits.network using (networkpk) where siteid = $2 and networkid = $1", s.networkID, s.siteID).Scan(&s.name) if err == sql.ErrNoRows { web.NotFound(w, r, "invalid siteID and networkID combination: "+s.siteID+" "+s.networkID) return sites, false } if err != nil { web.ServiceUnavailable(w, r, err) return sites, false } } return sites, true }
func alertLevel(w http.ResponseWriter, r *http.Request) { if len(r.URL.Query()) != 0 { web.BadRequest(w, r, "incorrect number of query parameters.") return } var d string err := db.QueryRow(`SELECT row_to_json(fc) FROM ( SELECT 'FeatureCollection' as type, array_to_json(array_agg(f)) as features FROM (SELECT 'Feature' as type, ST_AsGeoJSON(v.location)::json as geometry, row_to_json((SELECT l FROM ( SELECT id AS "volcanoID", title AS "volcanoTitle", alert_level as "level", activity, hazards ) as l )) as properties FROM (qrt.volcano JOIN qrt.volcanic_alert_level using (alert_level)) as v ) As f ) as fc`).Scan(&d) if err != nil { web.ServiceUnavailable(w, r, err) return } b := []byte(d) web.Ok(w, r, &b) }
func intensityReportedLatest(w http.ResponseWriter, r *http.Request) { if err := intensityReportedLatestD.CheckParams(r.URL.Query()); err != nil { web.BadRequest(w, r, err.Error()) return } if r.URL.Query().Get("type") != "reported" { web.BadRequest(w, r, "type must be reported.") return } zoom := r.URL.Query().Get("zoom") if !zoomRe.MatchString(r.URL.Query().Get("zoom")) { web.BadRequest(w, r, "Invalid zoom") return } var d string err := db.QueryRow( `SELECT row_to_json(fc) FROM ( SELECT 'FeatureCollection' as type, COALESCE(array_to_json(array_agg(f)), '[]') as features FROM (SELECT 'Feature' as type, ST_AsGeoJSON(s.location)::json as geometry, row_to_json(( select l from ( select max_mmi, min_mmi, count ) as l )) as properties from (select st_pointfromgeohash(geohash` + zoom + `) as location, min(mmi) as min_mmi, max(mmi) as max_mmi, count(mmi) as count FROM impact.intensity_reported WHERE time >= (now() - interval '60 minutes') group by (geohash` + zoom + `)) as s ) As f ) as fc`).Scan(&d) if err != nil { web.ServiceUnavailable(w, r, err) return } b := []byte(d) web.Ok(w, r, &b) }
func siteType(w http.ResponseWriter, r *http.Request) { if err := siteTypeD.CheckParams(r.URL.Query()); err != nil { web.BadRequest(w, r, err.Error()) return } v := r.URL.Query() if v.Get("methodID") != "" && v.Get("typeID") == "" { web.BadRequest(w, r, "typeID must be specified when methodID is specified.") return } var typeID, methodID, within string if v.Get("typeID") != "" { typeID = v.Get("typeID") if !validType(w, r, typeID) { return } if v.Get("methodID") != "" { methodID = v.Get("methodID") if !validTypeMethod(w, r, typeID, methodID) { return } } } if v.Get("within") != "" { within = strings.Replace(v.Get("within"), "+", "", -1) if !validPoly(w, r, within) { return } } w.Header().Set("Content-Type", web.V1GeoJSON) b, err := geoJSONSites(typeID, methodID, within) if err != nil { web.ServiceUnavailable(w, r, err) return } web.Ok(w, r, &b) }
func getStddev(w http.ResponseWriter, r *http.Request) (string, bool) { t := r.URL.Query().Get("stddev") switch t { case ``, `pop`: return t, true default: web.BadRequest(w, r, "invalid stddev type") return ``, false } }
func getSparkLabel(w http.ResponseWriter, r *http.Request) (string, bool) { t := r.URL.Query().Get("label") switch t { case ``, `all`, `none`, `latest`: return t, true default: web.BadRequest(w, r, "invalid label") return ``, false } }
func getPlotType(w http.ResponseWriter, r *http.Request) (string, bool) { t := r.URL.Query().Get("type") switch t { case ``, `line`, `scatter`: return t, true default: web.BadRequest(w, r, "invalid plot type") return ``, false } }
func region(w http.ResponseWriter, r *http.Request) { if len(r.URL.Query()) != 0 { web.BadRequest(w, r, "incorrect number of query parameters.") return } regionID := r.URL.Path[regionLen:] var d string err := db.QueryRow("select regionname FROM qrt.region where regionname = $1", regionID).Scan(&d) if err == sql.ErrNoRows { web.BadRequest(w, r, "invalid regionID: "+regionID) return } if err != nil { web.ServiceUnavailable(w, r, err) return } err = db.QueryRow(`SELECT row_to_json(fc) FROM ( SELECT 'FeatureCollection' as type, array_to_json(array_agg(f)) as features FROM (SELECT 'Feature' as type, ST_AsGeoJSON(q.geom)::json as geometry, row_to_json((SELECT l FROM ( SELECT regionname as "regionID", title, groupname as group ) as l )) as properties FROM qrt.region as q where regionname = $1 ) as f ) as fc`, regionID).Scan(&d) if err != nil { web.ServiceUnavailable(w, r, err) return } w.Header().Set("Surrogate-Control", web.MaxAge86400) b := []byte(d) web.Ok(w, r, &b) }
/* Returns 0 if days not set */ func getDays(w http.ResponseWriter, r *http.Request) (int, bool) { var days int if r.URL.Query().Get("days") != "" { var err error days, err = strconv.Atoi(r.URL.Query().Get("days")) if err != nil || days > 365000 { web.BadRequest(w, r, "Invalid days query param.") return 0, false } } return days, true }
func router(w http.ResponseWriter, r *http.Request) { // requests that don't have a specific version header are routed to the latest version. var latest bool accept := r.Header.Get("Accept") switch accept { case web.V1GeoJSON, web.V1JSON, web.V1CSV: default: latest = true } switch { case r.URL.Path == "/plot": if r.URL.Query().Get("siteID") != "" { plotSite(w, r) } else { plotSites(w, r) } case r.URL.Path == "/spark": spark(w, r) case r.URL.Path == "/map/site": if r.URL.Query().Get("siteID") != "" { siteMap(w, r) } else if r.URL.Query().Get("sites") != "" { siteMap(w, r) } else { siteTypeMap(w, r) } case r.URL.Path == "/observation" && (accept == web.V1CSV || latest): if r.URL.Query().Get("siteID") != "" { observation(w, r) } else { spatialObs(w, r) } case r.URL.Path == "/observation/stats" && (accept == web.V1JSON || latest): observationStats(w, r) case r.URL.Path == "/site" && (accept == web.V1GeoJSON || latest): if r.URL.Query().Get("siteID") != "" { site(w, r) } else { siteType(w, r) } case r.URL.Path == "/type" && (accept == web.V1JSON || latest): typeH(w, r) case r.URL.Path == "/method" && (accept == web.V1JSON || latest): method(w, r) case strings.HasPrefix(r.URL.Path, apidoc.Path): docs.Serve(w, r) default: web.BadRequest(w, r, "Can't find a route for this request. Please refer to /api-docs") } }
/* Returns zero time if not set. */ func getStart(w http.ResponseWriter, r *http.Request) (time.Time, bool) { var t time.Time if r.URL.Query().Get("start") != "" { var err error t, err = time.Parse(time.RFC3339, r.URL.Query().Get("start")) if err != nil { web.BadRequest(w, r, "Invalid start query param.") return t, false } } return t, true }
func validPoly(w http.ResponseWriter, r *http.Request, poly string) bool { var b bool // There is a chance we will return an // invalid polygon error for a DB DIal error but in that case something // else is about to fail. Postgis errors are hard to handle via an sql error. err := db.QueryRow(`select ST_PolygonFromText($1, 4326) IS NOT NULL AS poly`, poly).Scan(&b) if b { return true } web.BadRequest(w, r, "invalid polygon: "+err.Error()) return false }
func felt(w http.ResponseWriter, r *http.Request) { if err := feltD.CheckParams(r.URL.Query()); err != nil { web.BadRequest(w, r, err.Error()) return } publicID := r.URL.Query().Get("publicID") var d string err := db.QueryRow("select publicid FROM qrt.quake_materialized where publicid = $1", publicID).Scan(&d) if err == sql.ErrNoRows { web.NotFound(w, r, "invalid publicID: "+publicID) return } if err != nil { web.ServiceUnavailable(w, r, err) return } res, err := client.Get(feltURL + publicID + ".geojson") defer res.Body.Close() if err != nil { web.ServiceUnavailable(w, r, err) return } b, err := ioutil.ReadAll(res.Body) if err != nil { web.ServiceUnavailable(w, r, err) return } // Felt returns a 400 when it should probably be a 404. Tapestry quirk? switch { case 200 == res.StatusCode: web.Ok(w, r, &b) return case 4 == res.StatusCode/100: web.NotFound(w, r, string(b)) return case 5 == res.StatusCode/500: web.ServiceUnavailable(w, r, errors.New("error proxying felt resports. Shrug.")) return } web.ServiceUnavailable(w, r, errors.New("unknown response from felt.")) }
func news(w http.ResponseWriter, r *http.Request) { if len(r.URL.Query()) != 0 { web.BadRequest(w, r, "incorrect number of query parameters.") return } j, err := fetchRSS(newsURL) if err != nil { web.ServiceUnavailable(w, r, err) return } w.Header().Set("Surrogate-Control", web.MaxAge300) web.Ok(w, r, &j) }
// validSrs checks that the srs represented by auth and srid exists in the DB. func validSrs(w http.ResponseWriter, r *http.Request, auth string, srid int) bool { var d string err := db.QueryRow(`select auth_name FROM public.spatial_ref_sys where auth_name = $1 AND srid = $2`, auth, srid).Scan(&d) if err == sql.ErrNoRows { web.BadRequest(w, r, "invalid srsName") return false } if err != nil { web.ServiceUnavailable(w, r, err) return false } return true }
func method(w http.ResponseWriter, r *http.Request) { if err := methodD.CheckParams(r.URL.Query()); err != nil { web.BadRequest(w, r, err.Error()) return } typeID := r.URL.Query().Get("typeID") if typeID != "" && !validType(w, r, typeID) { return } var d string var err error switch typeID { case "": err = db.QueryRow( `select row_to_json(fc) from (select array_to_json(array_agg(m)) as method from (select methodid as "methodID", method.name, method.description, method.reference from fits.type join fits.type_method using (typepk) join fits.method using (methodpk)) as m) as fc`).Scan(&d) default: err = db.QueryRow( `select row_to_json(fc) from (select array_to_json(array_agg(m)) as method from (select methodid as "methodID", method.name, method.description, method.reference from fits.type join fits.type_method using (typepk) join fits.method using (methodpk) where type.typeID = $1) as m) as fc`, typeID).Scan(&d) } if err != nil { web.ServiceUnavailable(w, r, err) return } w.Header().Set("Content-Type", web.V1JSON) b := []byte(d) web.Ok(w, r, &b) }
func typeH(w http.ResponseWriter, r *http.Request) { if len(r.URL.Query()) != 0 { web.BadRequest(w, r, "incorrect number of query params.") return } w.Header().Set("Content-Type", web.V1JSON) var d string err := db.QueryRow( `select row_to_json(fc) from (select array_to_json(array_agg(t)) as type from (select typeid as "typeID", type.name, symbol as unit, description from fits.type join fits.unit using (unitpk)) as t) as fc`).Scan(&d) if err != nil { web.ServiceUnavailable(w, r, err) return } b := []byte(d) web.Ok(w, r, &b) }
func site(w http.ResponseWriter, r *http.Request) { if err := siteD.CheckParams(r.URL.Query()); err != nil { web.BadRequest(w, r, err.Error()) return } networkID := r.URL.Query().Get("networkID") siteID := r.URL.Query().Get("siteID") if !validSite(w, r, networkID, siteID) { return } w.Header().Set("Content-Type", web.V1GeoJSON) b, err := geoJSONSite(networkID, siteID) if err != nil { web.ServiceUnavailable(w, r, err) return } web.Ok(w, r, &b) }
func intensityReported(w http.ResponseWriter, r *http.Request) { if err := intensityReportedD.CheckParams(r.URL.Query()); err != nil { web.BadRequest(w, r, err.Error()) return } if r.URL.Query().Get("type") != "reported" { web.BadRequest(w, r, "type must be reported.") return } zoom := r.URL.Query().Get("zoom") if !zoomRe.MatchString(r.URL.Query().Get("zoom")) { web.BadRequest(w, r, "Invalid zoom") return } // Check that the publicid exists in the DB. // If it does we keep the origintime - we need it later on. publicID := r.URL.Query().Get("publicID") originTime := time.Time{} err := db.QueryRow("select origintime FROM qrt.quake_materialized where publicid = $1", publicID).Scan(&originTime) if err == sql.ErrNoRows { web.NotFound(w, r, "invalid publicID: "+publicID) return } if err != nil { web.ServiceUnavailable(w, r, err) return } query := `SELECT row_to_json(fc) FROM ( SELECT 'FeatureCollection' as type, COALESCE(array_to_json(array_agg(f)), '[]') as features FROM (SELECT 'Feature' as type, ST_AsGeoJSON(s.location)::json as geometry, row_to_json(( select l from ( select max_mmi, min_mmi, count ) as l )) as properties from (select st_pointfromgeohash(geohash` + zoom + `) as location, min(mmi) as min_mmi, max(mmi) as max_mmi, count(mmi) as count FROM impact.intensity_reported WHERE time >= $1 AND time <= $2 group by (geohash` + zoom + `)) as s ) As f ) as fc` var d string err = db.QueryRow(query, originTime.Add(time.Duration(-1*time.Minute)), originTime.Add(time.Duration(15*time.Minute))).Scan(&d) if err != nil { web.ServiceUnavailable(w, r, err) return } b := []byte(d) web.Ok(w, r, &b) }
func router(w http.ResponseWriter, r *http.Request) { // requests that don't have a specific version header are routed to the latest version. var latest bool accept := r.Header.Get("Accept") switch accept { case web.V1GeoJSON, web.V1JSON: default: latest = true } switch { case strings.HasPrefix(r.URL.Path, "/quake") && (accept == web.V1GeoJSON || latest): w.Header().Set("Content-Type", web.V1GeoJSON) switch { case r.URL.Query().Get("intensity") != "": quakes(w, r) case r.URL.Query().Get("regionIntensity") != "": quakesRegion(w, r) case strings.HasPrefix(r.URL.Path, "/quake/"): quake(w, r) default: web.BadRequest(w, r, "Can't find a route for this request. Please refer to /api-docs") } case r.URL.Path == "/intensity" && (accept == web.V1GeoJSON || latest): w.Header().Set("Content-Type", web.V1GeoJSON) switch { case r.URL.Query().Get("type") == "measured": intensityMeasuredLatest(w, r) // case r.URL.Query().Get("type") == "reported" && r.URL.Query().Get("publicID") == "": // intensityReportedLatest(w, r) // case r.URL.Query().Get("type") == "reported" && r.URL.Query().Get("publicID") != "": // intensityReported(w, r) default: web.BadRequest(w, r, "Can't find a route for this request. Please refer to /api-docs") } case r.URL.Path == "/felt/report" && (accept == web.V1GeoJSON || latest): w.Header().Set("Content-Type", web.V1GeoJSON) felt(w, r) case r.URL.Path == "/volcano/alert/level" && (accept == web.V1GeoJSON || latest): w.Header().Set("Content-Type", web.V1GeoJSON) alertLevel(w, r) case r.URL.Path == "/volcano/alert/bulletin" && (accept == web.V1JSON || latest): w.Header().Set("Content-Type", web.V1JSON) alertBulletin(w, r) case strings.HasPrefix(r.URL.Path, "/region/") && (accept == web.V1GeoJSON || latest): w.Header().Set("Content-Type", web.V1GeoJSON) region(w, r) case r.URL.Path == "/region" && (accept == web.V1GeoJSON || latest): w.Header().Set("Content-Type", web.V1GeoJSON) regions(w, r) case r.URL.Path == "/news/geonet" && (accept == web.V1JSON || latest): w.Header().Set("Content-Type", web.V1JSON) news(w, r) case strings.HasPrefix(r.URL.Path, apidoc.Path): docs.Serve(w, r) case r.URL.Path == "/soh": soh(w, r) case r.URL.Path == "/soh/impact": impactSOH(w, r) default: web.BadRequest(w, r, "Can't find a route for this request. Please refer to /api-docs") } }
func siteMap(w http.ResponseWriter, r *http.Request) { if err := siteMapD.CheckParams(r.URL.Query()); err != nil { web.BadRequest(w, r, err.Error()) return } v := r.URL.Query() bbox := v.Get("bbox") var insetBbox string if v.Get("insetBbox") != "" { insetBbox = v.Get("insetBbox") err := map180.ValidBbox(insetBbox) if err != nil { web.BadRequest(w, r, err.Error()) return } } if v.Get("sites") == "" && (v.Get("siteID") == "" && v.Get("networkID") == "") { web.BadRequest(w, r, "please specify sites or networkID and siteID") return } if v.Get("sites") != "" && (v.Get("siteID") != "" || v.Get("networkID") != "") { web.BadRequest(w, r, "please specify either sites or networkID and siteID") return } if v.Get("sites") == "" && (v.Get("siteID") == "" || v.Get("networkID") == "") { web.BadRequest(w, r, "please specify networkID and siteID") return } err := map180.ValidBbox(bbox) if err != nil { web.BadRequest(w, r, err.Error()) return } width := 130 if v.Get("width") != "" { width, err = strconv.Atoi(v.Get("width")) if err != nil { web.BadRequest(w, r, "invalid width.") return } } var s []st if v.Get("sites") != "" { for _, ns := range strings.Split(v.Get("sites"), ",") { nss := strings.Split(ns, ".") if len(nss) != 2 { web.BadRequest(w, r, "invalid sites query.") return } s = append(s, st{networkID: nss[0], siteID: nss[1]}) } } else { s = append(s, st{networkID: v.Get("networkID"), siteID: v.Get("siteID")}) } markers := make([]map180.Marker, 0) for _, site := range s { if !validSite(w, r, site.networkID, site.siteID) { return } g, err := geoJSONSite(site.networkID, site.siteID) if err != nil { web.ServiceUnavailable(w, r, err) return } m, err := geoJSONToMarkers(g) if err != nil { web.ServiceUnavailable(w, r, err) return } markers = append(markers, m...) } b, err := wm.SVG(bbox, width, markers, insetBbox) if err != nil { web.ServiceUnavailable(w, r, err) return } w.Header().Set("Content-Type", "image/svg+xml") web.OkBuf(w, r, &b) }
/** * query end point for observation statistics including: max, min, mean, std, first and last values * http://fits.geonet.org.nz/observation/stats?typeID=e&siteID=HOLD&networkID=CG&days=100 */ func observationStats(w http.ResponseWriter, r *http.Request) { //1. check query parameters if err := observationD.CheckParams(r.URL.Query()); err != nil { web.BadRequest(w, r, err.Error()) return } v := r.URL.Query() typeID := v.Get("typeID") networkID := v.Get("networkID") siteID := v.Get("siteID") if !validType(w, r, typeID) { return } var days int var err error var tmin, tmax time.Time if v.Get("days") != "" { days, err = strconv.Atoi(v.Get("days")) if err != nil || days > 365000 { web.BadRequest(w, r, "Invalid days query param.") return } tmax = time.Now().UTC() tmin = tmax.Add(time.Duration(days*-1) * time.Hour * 24) } var methodID string if v.Get("methodID") != "" { methodID = v.Get("methodID") if !validTypeMethod(w, r, typeID, methodID) { return } } //2. Find the unit var unit string err = db.QueryRow("select symbol FROM fits.type join fits.unit using (unitPK) where typeID = $1", typeID).Scan(&unit) if err == sql.ErrNoRows { web.NotFound(w, r, "unit not found for typeID: "+typeID) return } if err != nil { web.ServiceUnavailable(w, r, err) return } // retrieve values using existing functions values, err := loadObs(networkID, siteID, typeID, methodID, tmin) if err != nil { web.ServiceUnavailable(w, r, err) return } mean, stdDev, err := stddevPop(networkID, siteID, typeID, methodID, tmin) if err != nil { web.ServiceUnavailable(w, r, err) return } stats := obstats{Unit: unit, Mean: mean, StddevPopulation: stdDev} //4. get maximum, minimum, first, last values stats.First = values[0] stats.Last = values[len(values)-1] iMin, iMax, _ := extremes(values) stats.Minimum = values[iMin] stats.Maximum = values[iMax] //5. send result response w.Header().Set("Content-Type", web.V1JSON) b, err := json.Marshal(stats) if err != nil { web.ServiceUnavailable(w, r, err) return } web.Ok(w, r, &b) }
func observation(w http.ResponseWriter, r *http.Request) { if err := observationD.CheckParams(r.URL.Query()); err != nil { web.BadRequest(w, r, err.Error()) return } v := r.URL.Query() typeID := v.Get("typeID") networkID := v.Get("networkID") siteID := v.Get("siteID") if !validType(w, r, typeID) { return } var days int if v.Get("days") != "" { var err error days, err = strconv.Atoi(v.Get("days")) if err != nil || days > 365000 { web.BadRequest(w, r, "Invalid days query param.") return } } var methodID string if v.Get("methodID") != "" { methodID = v.Get("methodID") if !validTypeMethod(w, r, typeID, methodID) { return } } // Find the unit for the CSV header var unit string err := db.QueryRow("select symbol FROM fits.type join fits.unit using (unitPK) where typeID = $1", typeID).Scan(&unit) if err == sql.ErrNoRows { web.NotFound(w, r, "unit not found for typeID: "+typeID) return } if err != nil { web.ServiceUnavailable(w, r, err) return } var d string var rows *sql.Rows switch { case days == 0 && methodID == "": rows, err = db.Query( `SELECT format('%s,%s,%s', to_char(time, 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'), value, error) as csv FROM fits.observation WHERE sitepk = ( SELECT DISTINCT ON (sitepk) sitepk from fits.site join fits.network using (networkpk) where siteid = $2 and networkid = $1 ) AND typepk = ( SELECT typepk FROM fits.type WHERE typeid = $3 ) ORDER BY time ASC;`, networkID, siteID, typeID) case days != 0 && methodID == "": rows, err = db.Query( `SELECT format('%s,%s,%s', to_char(time, 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'), value, error) as csv FROM fits.observation WHERE sitepk = ( SELECT DISTINCT ON (sitepk) sitepk from fits.site join fits.network using (networkpk) where siteid = $2 and networkid = $1 ) AND typepk = ( SELECT typepk FROM fits.type WHERE typeid = $3 ) AND time > (now() - interval '`+strconv.Itoa(days)+` days') ORDER BY time ASC;`, networkID, siteID, typeID) case days == 0 && methodID != "": rows, err = db.Query( `SELECT format('%s,%s,%s', to_char(time, 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'), value, error) as csv FROM fits.observation WHERE sitepk = ( SELECT DISTINCT ON (sitepk) sitepk from fits.site join fits.network using (networkpk) where siteid = $2 and networkid = $1 ) AND typepk = ( SELECT typepk FROM fits.type WHERE typeid = $3 ) AND methodpk = ( SELECT methodpk FROM fits.method WHERE methodid = $4 ) ORDER BY time ASC;`, networkID, siteID, typeID, methodID) case days != 0 && methodID != "": rows, err = db.Query( `SELECT format('%s,%s,%s', to_char(time, 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'), value, error) as csv FROM fits.observation WHERE sitepk = ( SELECT DISTINCT ON (sitepk) sitepk from fits.site join fits.network using (networkpk) where siteid = $2 and networkid = $1 ) AND typepk = ( SELECT typepk FROM fits.type WHERE typeid = $3 ) AND methodpk = ( SELECT methodpk FROM fits.method WHERE methodid = $4 ) AND time > (now() - interval '`+strconv.Itoa(days)+` days') ORDER BY time ASC;`, networkID, siteID, typeID, methodID) } if err != nil { web.ServiceUnavailable(w, r, err) return } defer rows.Close() // Use a buffer for reading the data from the DB. Then if a there // is an error we can let the client know without sending // a partial data response. var b bytes.Buffer b.Write([]byte("date-time, " + typeID + " (" + unit + "), error (" + unit + ")")) b.Write(eol) for rows.Next() { err := rows.Scan(&d) if err != nil { web.ServiceUnavailable(w, r, err) return } b.Write([]byte(d)) b.Write(eol) } rows.Close() if methodID != "" { w.Header().Set("Content-Disposition", `attachment; filename="FITS-`+networkID+`-`+siteID+`-`+typeID+`-`+methodID+`.csv"`) } else { w.Header().Set("Content-Disposition", `attachment; filename="FITS-`+networkID+`-`+siteID+`-`+typeID+`.csv"`) } w.Header().Set("Content-Type", web.V1CSV) web.OkBuf(w, r, &b) }
func plotSite(w http.ResponseWriter, r *http.Request) { if err := plotSiteD.CheckParams(r.URL.Query()); err != nil { web.BadRequest(w, r, err.Error()) return } var plotType string var s siteQ var t typeQ var start time.Time var days int var ymin, ymax float64 var showMethod bool var stddev string var ok bool if plotType, ok = getPlotType(w, r); !ok { return } if showMethod, ok = getShowMethod(w, r); !ok { return } if stddev, ok = getStddev(w, r); !ok { return } if start, ok = getStart(w, r); !ok { return } if days, ok = getDays(w, r); !ok { return } if ymin, ymax, ok = getYRange(w, r); !ok { return } if t, ok = getType(w, r); !ok { return } if s, ok = getSite(w, r); !ok { return } var p plt switch { case start.IsZero() && days == 0: // do nothing - autorange on the data. case start.IsZero() && days > 0: n := time.Now().UTC() start = n.Add(time.Duration(days*-1) * time.Hour * 24) p.SetXAxis(start, n) days = 0 // add all data > than start by setting 0. Allows for adding start end to URL. case !start.IsZero() && days > 0: p.SetXAxis(start, start.Add(time.Duration(days*1)*time.Hour*24)) case !start.IsZero() && days == 0: web.BadRequest(w, r, "Invalid start specified without days") return } switch { case ymin == 0 && ymax == 0: case ymin == ymax: p.SetYRange(ymin) default: p.SetYAxis(ymin, ymax) } p.SetTitle(fmt.Sprintf("%s (%s) - %s", s.siteID, s.name, t.description)) p.SetUnit(t.unit) p.SetYLabel(fmt.Sprintf("%s (%s)", t.name, t.unit)) var err error switch showMethod { case false: err = p.addSeries(t, start, days, s) case true: err = p.addSeriesLabelMethod(t, start, days, s) } if err != nil { web.ServiceUnavailable(w, r, err) return } if stddev == `pop` { err = p.setStddevPop(s, t, start, days) } if err != nil { web.ServiceUnavailable(w, r, err) return } b := new(bytes.Buffer) switch plotType { case ``, `line`: err = ts.Line.Draw(p.Plot, b) case `scatter`: err = ts.Scatter.Draw(p.Plot, b) } if err != nil { web.ServiceUnavailable(w, r, err) return } w.Header().Set("Content-Type", "image/svg+xml") web.OkBuf(w, r, b) }
func spark(w http.ResponseWriter, r *http.Request) { if err := sparkD.CheckParams(r.URL.Query()); err != nil { web.BadRequest(w, r, err.Error()) return } var plotType string var s siteQ var t typeQ var days int var ymin, ymax float64 var stddev string var label string var ok bool if plotType, ok = getPlotType(w, r); !ok { return } if stddev, ok = getStddev(w, r); !ok { return } if label, ok = getSparkLabel(w, r); !ok { return } if days, ok = getDays(w, r); !ok { return } if ymin, ymax, ok = getYRange(w, r); !ok { return } if t, ok = getType(w, r); !ok { return } if s, ok = getSite(w, r); !ok { return } var p plt var tmin time.Time if days > 0 { n := time.Now().UTC() tmin = n.Add(time.Duration(days*-1) * time.Hour * 24) p.SetXAxis(tmin, n) days = 0 // add all data > than tmin } switch { case ymin == 0 && ymax == 0: case ymin == ymax: p.SetYRange(ymin) default: p.SetYAxis(ymin, ymax) } p.SetUnit(t.unit) var err error if stddev == `pop` { err = p.setStddevPop(s, t, tmin, days) } if err != nil { web.ServiceUnavailable(w, r, err) return } err = p.addSeries(t, tmin, days, s) if err != nil { web.ServiceUnavailable(w, r, err) return } b := new(bytes.Buffer) switch plotType { case ``, `line`: switch label { case ``, `all`: err = ts.SparkLineAll.Draw(p.Plot, b) case `latest`: err = ts.SparkLineLatest.Draw(p.Plot, b) case `none`: err = ts.SparkLineNone.Draw(p.Plot, b) } case `scatter`: switch label { case ``, `all`: err = ts.SparkScatterAll.Draw(p.Plot, b) case `latest`: err = ts.SparkScatterLatest.Draw(p.Plot, b) case `none`: err = ts.SparkScatterNone.Draw(p.Plot, b) } } if err != nil { web.ServiceUnavailable(w, r, err) return } w.Header().Set("Content-Type", "image/svg+xml") web.OkBuf(w, r, b) }
func quakes(w http.ResponseWriter, r *http.Request) { if err := quakesD.CheckParams(r.URL.Query()); err != nil { web.BadRequest(w, r, err.Error()) return } v := r.URL.Query() number := v.Get("number") regionID := v.Get("regionID") intensity := v.Get("intensity") quality := strings.Split(v.Get("quality"), ",") if !numberRe.MatchString(number) { web.BadRequest(w, r, "Invalid query parameter number: "+number) return } if !intensityRe.MatchString(intensity) { web.BadRequest(w, r, "Invalid intensity: "+intensity) return } for _, q := range quality { if !qualityRe.MatchString(q) { web.BadRequest(w, r, "Invalid quality: "+q) return } } var d string err := db.QueryRow("select regionname FROM qrt.region where regionname = $1 AND groupname in ('region', 'north', 'south')", regionID).Scan(&d) if err == sql.ErrNoRows { web.BadRequest(w, r, "invalid quake regionID: "+regionID) return } if err != nil { web.ServiceUnavailable(w, r, err) return } err = db.QueryRow( `SELECT row_to_json(fc) FROM ( SELECT 'FeatureCollection' as type, COALESCE(array_to_json(array_agg(f)), '[]') as features FROM (SELECT 'Feature' as type, ST_AsGeoJSON(q.origin_geom)::json as geometry, row_to_json((SELECT l FROM ( SELECT publicid AS "publicID", to_char(origintime, 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"') as "time", depth, magnitude, type, agency, locality, intensity, intensity_`+regionID+` as "regionIntensity", quality, to_char(updatetime, 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"') as "modificationTime" ) as l )) as properties FROM qrt.quakeinternal_v2 as q where maxmmi >= qrt.intensity_to_mmi($1) AND quality in ('`+strings.Join(quality, `','`)+`') AND ST_Contains((select geom from qrt.region where regionname = $3), ST_Shift_Longitude(origin_geom)) limit $2 ) as f ) as fc`, intensity, number, regionID).Scan(&d) if err != nil { web.ServiceUnavailable(w, r, err) return } b := []byte(d) web.Ok(w, r, &b) }