Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add geojson search endpoint #2174

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
32 changes: 32 additions & 0 deletions app/controllers/api/v3/search.rb
Expand Up @@ -52,6 +52,38 @@ def forwarded_ip_address
serialized_bikes_results(paginate(Bike.search(interpreted_params)))
end

desc "Get stolen GeoJSON formatted response, for mapping"
params do
optional :query, type: String, desc: "Full text search"
requires :lat, type: String, desc: "Latitude"
requires :lng, type: String, desc: "Longitude"
optional :distance, type: String, desc: "Distance in miles from coordinates", default: 10
optional :limit, type: Integer, default: 100, desc: "Stolen Bikes returned (max 500)"
end
get "/geojson" do
limit = params[:limit].to_i
limit = if limit.blank? || limit < 1
100
else
limit > 500 ? 500 : limit
end
distance = params[:distance].to_i
distance = 10 if distance.blank? || distance < 1
bounding_box = Geocoder::Calculations.bounding_box([params[:lat], params[:lng]], distance)
bikes = if bounding_box.detect(&:nan?) # If we can't create a bounding box, no bikes
Bike.none
else
Bike.unscoped.status_stolen.current.where.not(latitude: nil)
.within_bounding_box(bounding_box).order(occurred_at: :desc)
end
bikes = bikes.pg_search(params[:query]) if params[:query].present?
{
type: "FeatureCollection",
features: bikes.limit(limit).pluck(:id, :occurred_at, :latitude, :longitude)
.map { |id, oc, lat, lng| BikeGeojsoner.feature_from_plucked(id, oc, lat, lng) }
}
end

desc "Count of bikes matching search", {
notes: <<-NOTE
Include all the options passed in your search. This endpoint accepts the same parameters as the root `/search` endpoint.
Expand Down
31 changes: 31 additions & 0 deletions spec/requests/api/v3/search_request_spec.rb
Expand Up @@ -20,6 +20,37 @@
end
end

describe "/geojson" do
include_context :geocoder_default_location
let!(:bike) { FactoryBot.create(:bike, manufacturer: manufacturer) }
let!(:bike2) { FactoryBot.create(:stolen_bike, manufacturer: manufacturer) }
let(:target) do
{
type: "Feature",
properties: {id: bike2.id, color: "#BD1622"},
geometry: {type: "Point", coordinates: [-74.01,40.71]}
}
end
context "with per_page" do
it "returns matching bikes, defaults to stolen" do
expect(Bike.count).to eq 2
get "/api/v3/search/geojson?lat=#{default_location[:latitude]}&lng=#{default_location[:longitude]}"
result = JSON.parse(response.body)
expect(result.keys).to match_array(["type", "features"])
expect(result["type"]).to eq "FeatureCollection"
expect(result["features"].count).to eq 1
expect_hashes_to_match(result["features"].first, target)
expect(response.headers["Access-Control-Allow-Origin"]).to eq("*")
expect(response.headers["Access-Control-Request-Method"]).to eq("*")

get "/api/v3/search/geojson?lat=#{default_location[:latitude]}&lng=#{default_location[:longitude]}&query=#{manufacturer.name}"
result2 = JSON.parse(response.body)
expect(result2["features"].count).to eq 1
expect_hashes_to_match(result2["features"].first, target)
end
end
end

describe "/close_serials" do
let!(:bike) { FactoryBot.create(:bike, manufacturer: manufacturer, serial_number: "something") }
let(:query_params) { {serial: "somethind", stolenness: "non"} }
Expand Down