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.")) }
/* 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 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) }
// validTypeMethod checks that the typeID and methodID exists in the DB // and are a valid combination. func validTypeMethod(w http.ResponseWriter, r *http.Request, typeID, methodID string) bool { var d string err := db.QueryRow("SELECT typepk FROM fits.type join fits.type_method using (typepk) join fits.method using (methodpk) WHERE typeid = $1 and methodid = $2", typeID, methodID).Scan(&d) if err == sql.ErrNoRows { web.NotFound(w, r, "invalid methodID for typeID: "+methodID+" "+typeID) return false } if err != nil { web.ServiceUnavailable(w, r, err) return false } return true }
// validType checks that the typeID exists in the DB. func validType(w http.ResponseWriter, r *http.Request, typeID string) bool { var d string err := db.QueryRow("select typeID FROM fits.type where typeID = $1", typeID).Scan(&d) if err == sql.ErrNoRows { web.NotFound(w, r, "invalid typeID: "+typeID) return false } if err != nil { web.ServiceUnavailable(w, r, err) return false } return true }
// validSite checks that the siteID and networkID combination exists in the DB. func validSite(w http.ResponseWriter, r *http.Request, networkID, siteID string) bool { var d string err := db.QueryRow("select siteID FROM fits.site join fits.network using (networkpk) where siteid = $2 and networkid = $1", networkID, siteID).Scan(&d) if err == sql.ErrNoRows { web.NotFound(w, r, "invalid siteID and networkID combination: "+siteID+" "+networkID) return false } if err != nil { web.ServiceUnavailable(w, r, err) return false } return true }
func getSite(w http.ResponseWriter, r *http.Request) (siteQ, bool) { s := siteQ{ networkID: r.URL.Query().Get("networkID"), siteID: r.URL.Query().Get("siteID"), } 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 s, false } if err != nil { web.ServiceUnavailable(w, r, err) return s, false } return s, true }
func getType(w http.ResponseWriter, r *http.Request) (typeQ, bool) { t := typeQ{ typeID: r.URL.Query().Get("typeID"), } err := db.QueryRow("select type.name, type.description, unit.symbol FROM fits.type join fits.unit using (unitpk) where typeID = $1", t.typeID).Scan(&t.name, &t.description, &t.unit) if err == sql.ErrNoRows { web.NotFound(w, r, "invalid typeID: "+t.typeID) return t, false } if err != nil { web.ServiceUnavailable(w, r, err) return t, false } return t, true }
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 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) }
/** * 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 spatialObs(w http.ResponseWriter, r *http.Request) { if err := spatialObsD.CheckParams(r.URL.Query()); err != nil { web.BadRequest(w, r, err.Error()) return } v := r.URL.Query() var err error var days int days, err = strconv.Atoi(v.Get("days")) if err != nil || days > 7 || days <= 0 { web.BadRequest(w, r, "Invalid days query param.") return } start, err := time.Parse(time.RFC3339, v.Get("start")) if err != nil { web.BadRequest(w, r, "Invalid start query param.") return } end := start.Add(time.Duration(days) * time.Hour * 24) var srsName, authName string var srid int if v.Get("srsName") != "" { srsName = v.Get("srsName") srs := strings.Split(srsName, ":") if len(srs) != 2 { web.BadRequest(w, r, "Invalid srsName.") return } authName = srs[0] var err error srid, err = strconv.Atoi(srs[1]) if err != nil { web.BadRequest(w, r, "Invalid srsName.") return } if !validSrs(w, r, authName, srid) { return } } else { srid = 4326 authName = "EPSG" srsName = "EPSG:4326" } typeID := v.Get("typeID") var methodID string if v.Get("methodID") != "" { methodID = v.Get("methodID") if !validTypeMethod(w, r, typeID, methodID) { return } } var within string if v.Get("within") != "" { within = strings.Replace(v.Get("within"), "+", "", -1) if !validPoly(w, r, within) { return } } 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 within == "" && methodID == "": rows, err = db.Query( `SELECT format('%s,%s,%s,%s,%s,%s,%s,%s,%s', networkid, siteid, ST_X(ST_Transform(location::geometry, $4)), ST_Y(ST_Transform(location::geometry, $4)), height,ground_relationship, to_char(time, 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'), value, error) as csv FROM fits.observation join fits.site using (sitepk) join fits.network using (networkpk) WHERE typepk = (SELECT typepk FROM fits.type WHERE typeid = $1) AND time >= $2 and time < $3 order by siteid asc`, typeID, start, end, srid) case within != "" && methodID == "": rows, err = db.Query( `SELECT format('%s,%s,%s,%s,%s,%s,%s,%s,%s', networkid, siteid, ST_X(ST_Transform(location::geometry, $4)), ST_Y(ST_Transform(location::geometry, $4)), height,ground_relationship, to_char(time, 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'), value, error) as csv FROM fits.observation join fits.site using (sitepk) join fits.network using (networkpk) WHERE typepk = (SELECT typepk FROM fits.type WHERE typeid = $1) AND ST_Within(location::geometry, ST_GeomFromText($5, 4326)) AND time >= $2 and time < $3 order by siteid asc`, typeID, start, end, srid, within) case within == "" && methodID != "": rows, err = db.Query( `SELECT format('%s,%s,%s,%s,%s,%s,%s,%s,%s', networkid, siteid, ST_X(ST_Transform(location::geometry, $4)), ST_Y(ST_Transform(location::geometry, $4)), height,ground_relationship, to_char(time, 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'), value, error) as csv FROM fits.observation join fits.site using (sitepk) join fits.network using (networkpk) WHERE typepk = (SELECT typepk FROM fits.type WHERE typeid = $1) AND methodpk = (SELECT methodpk FROM fits.method WHERE methodid = $5) AND time >= $2 and time < $3 order by siteid asc`, typeID, start, end, srid, methodID) case within != "" && methodID != "": rows, err = db.Query( `SELECT format('%s,%s,%s,%s,%s,%s,%s,%s,%s', networkid, siteid, ST_X(ST_Transform(location::geometry, $4)), ST_Y(ST_Transform(location::geometry, $4)), height,ground_relationship, to_char(time, 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'), value, error) as csv FROM fits.observation join fits.site using (sitepk) join fits.network using (networkpk) WHERE typepk = (SELECT typepk FROM fits.type WHERE typeid = $1) AND methodpk = (SELECT methodpk FROM fits.method WHERE methodid = $6) AND ST_Within(location::geometry, ST_GeomFromText($5, 4326)) AND time >= $2 and time < $3 order by siteid asc`, typeID, start, end, srid, within, methodID) } if err != nil { // not sure what a transformation error would look like. // Return any errors as a 404. Could improve this by inspecting // the error type to check for net dial errors that shoud 503. web.NotFound(w, r, err.Error()) return } defer rows.Close() var b bytes.Buffer b.Write([]byte("networkID, siteID, X (" + srsName + "), Y (" + srsName + "), height, groundRelationship, 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() w.Header().Set("Content-Type", web.V1CSV) if methodID != "" { w.Header().Set("Content-Disposition", `attachment; filename="FITS-`+typeID+`-`+methodID+`.csv"`) } else { w.Header().Set("Content-Disposition", `attachment; filename="FITS-`+typeID+`.csv"`) } web.OkBuf(w, r, &b) }