How to check if lat long is in the city limits

2020-06-25 04:46发布

问题:

How can I check if my lat/long is in the city limits or example, Greater London is enclosed by:

[bbox=-0.489,51.28,0.236,51.686]

Source :

http://wiki.openstreetmap.org/wiki/Bounding_Box

How can I check if a location (lat/lon):

51.55238,0.047032

Is there a gem already for this? Or what would be the best way to do this?

Bounty update:

I have a working solution but I feel its not the right one, I'm using geocoder gem, and my user has geocoded_by, and lat/long attributes. So here is how I do it :

def self.user_from_dublin(user_id)
  near([53.349937,-6.261917], 15).pluck(:id).include?(user_id)
end

So this loads all users from Dublin and compares the id of the user to those users in Dublin. If the id is found the user is considered to be from Dublin.

I don't think this will work well when there is more users.

Update

I'm using postgres database

回答1:

If its a boolean why not use where to limit the results to only your user. This way it will just involve one SQL query that will either be empty or have a size of 1 (your user being the only record)

def self.user_from_dublin(user_id)
  !(near([53.349937,-6.261917], 15).where(id: user_id).empty?)
end

empty? will be true when your user is not within your boundary, so the ! at the beginning will negate this and your function will return false when the user is not within the boundary.



回答2:

Maybe this is an overkill, but if you are using postgres you can install postgis extension to manage spatial data. Then in a irb you can do something like this:

result = ActiveRecord::Base.connection.execute('SELECT
ST_Contains(ST_SetSRID(ST_MakeBox2D(ST_Point(-0.489, 51.28), ST_Point(0.236, 51.686)), 4326),
ST_SetSRID(ST_Point(-0.1265, 51.483), 4326))')

The query is checking if the point is inside the given bbox using the ST_contains function

This will return:

=> #<PG::Result:0x007fa517fcbe08 @connection=#<PG::Connection:0x007fa5167f8970 @socket_io=nil, @notice_receiver=nil, @notice_processor=nil>>

Then you can do:

result.first

This will return:

{"st_contains"=>"t"}

With this point -0.7265, 44.483 (a point outside the bbox) the result will be:

{"st_contains"=>"f"}

If you don't want to use raw sql you can use gems to manage spatial data. A very good one is: rgeo. I recommend to read the creator's blog

Using rgeo you can define attributes for your models with "geotypes" like points, polygons, etc. and then use functions like contains?

I leave you with a gist with very basic instructions to install postgis with ubuntu.



回答3:

Can you use raw sql? The <@ geometric operator will check if the point is contained in the box

select '51.55238,0.047032'::point <@ '-0.489,51.28,0.236,51.686'::box;
 ?column? 
----------
 f


回答4:

You don't need anything special for this.

Bounding Box is a rectangle:

bbox = left,bottom,right,top

  +-----------(10,0)
  |              |
  |              |
  |              |
  |              |
  |              |
(0,10)-----------+

The weird (left-bottom) to (top-right) numbering is because, Longitude is measured left-to-right (to simplify, -180° is left of the globe, +180° is right of the globe), whereas Latitude is measured bottom-to-top (to simplify, -90° is bottom of the globe, +90° is top of the globe).

So as per the example,

(0,10)   = left, bottom = min longitude, min latitude
(10,10)  = right, top   = max longitude, max latitude

Now you want to know whether a point (5,5) falls inside it.

  +-----------(10,10)
  |              |
  |              |
  |     (5,5)    |
  |              |
  |              |
(0,0)------------+

So all you have to do is just create a table for cities in Postgres (or any database for that matter):

CREATE TABLE cities(
  city_name      VARCHAR(xxx),
  min_longitude  DECIMAL,
  min_latitude   DECIMAL,
  max_longitude  DECIMAL,
  max_latitude   DECIMAL
);

Now suppose you want to find out which city falls under a position latitude=5, longitude=6, then just use the following query:

SELECT city_name 
FROM cities 
WHERE (min_latitude  >= 6 AND max_latitude  <= 6)
AND   (min_longitude >= 5 AND max_longitude <= 5)

Substitute 5 and 6 with the user's latitude/longitude, and you will get the city where the user falls under.

EDIT: Small mix-up, minor formatting



回答5:

You can use this Gem https://github.com/square/border_patrol

First you need a small KLM with the limits of the city you want



回答6:

A KISS solution might be helpful since you have lat and lon attributes to the user model.

If you have London's box you could form an activerecord scope which would implement

LONDON_BOX = [-0.489,51.28,0.236,51.686]
scope :inside_london -> {
  where(lat: LONDON_BOX[1]..LONDON_BOX[3]).
  where(lon: LONDON_BOX[0]..LONDON_BOX[2])
}

thus enabling you to use
User.inside_london

Keep in mind that this type of double range query will not perform that good, it cannot use a proper index in MySQL. For that @david has provided a useful comment, the use of mongodb and its geospatial abilities will help. (But a DB change is a serious decision)



回答7:

BoundBox is rectangle and not an exact city boundary. And it's not efficient to calculate city boundary in each query. I think you can save city relation in callback before save record using geocoder. Please share your solution if you have better one.



回答8:

I've made a few assumptions, let me know if they are true:

Assumption #1: This lat/long represents where a user lives and will be updated infrequently when a user physically moves his home.

Assumption #2: This lat/long does not represent a users current position and will be updated constantly

Assumption #3: You have bounding boxes for all of the cities you wish to include in the system

Given these assumptions, I think the best way to handle this is to place each person into "City" buckets. Create a city model with a many to many relationship with user:

class City < ActiveRecord::Base
  has_many :user_city
  has_many :users, :through => :user_city

  attr_accessible :name, :bounding_box #bounding box is an array with 4 values which represent the corners of the city limits.
end

class User < ActiveRecord::Base
  after_validation :geocode 
  after_create :assign_city

  has_many :user_city
  has_many :city, :through => :user_city

  attr_accessible :email, :created_at, :password, :updated_at, :latitude, :longitude

  def assign_city
    City.all.each do |city|
      inCity = self.within_bounding_box(city.bounding_box)
      if inCity
       UserCity.create(:user=>self,:city=>city)
       break
      end
    end
  end

end

class UserCity < ActiveRecord::Base
  belongs_to :user
  belongs_to :city

  attr_accessible :user, :city
end

Now you can query users in a city with:

User.joins(:city).where({:cities=>{:name=>"London"}})


回答9:

You don't exactly say... but if what you want is the name of the nearest city to a Lat/Lon coordinate anywhere, then you could use google maps Geocoder services - set up a GeocoderRequest with a LatLng to search and issue a request.