Login or Sign Up to become a member!
LessThanDot Sit Logo

LessThanDot

Data Management

Less Than Dot is a community of passionate IT professionals and enthusiasts dedicated to sharing technical knowledge, experience, and assistance. Inside you will find reference materials, interesting technical discussions, and expert tips and commentary. Once you register for an account you will have immediate access to the forums and all past articles and commentaries.

LTD Social Sitings

Lessthandot twitter Lessthandot Linkedin Lessthandot friendfeed Lessthandot facebook Lessthandot rss

Note: Watch for social icons on posts by your favorite authors to follow their postings on these and other social sites.

Your profile

    Search

    XML Feeds

    Google Ads

    « SQL Server 2008 Proximity Search With The Geography Data TypeKaspersky Web Site Hacked With SQL Injection, How Embarrassing Is This? »
    comments

    Have you ever wanted to add functionality that allows you to perform simple radius searches based on Zip Code? For example, Show me all the cities within 20 miles of a certain zip code or show me the 5 closest retail locations. You can easily add this functionality to your database by importing a zip code database and writing a little code. This article will show you where to get zip code data, how to import it to your database, and how to use the data to perform proximity searches.

    Denis and I decided to show you how you can do simple radius searches based on Zip Codes, he did the SQL 2008 version here: http://blogs.lessthandot.com/index.php/DataMgmt/DataDesign/sql-server-2008-proximity-search-with-th (using the geography data type) and I did the SQL Server 2000/2005 version.

    The first thing we need to do is load our data. There are various sources for this data, ranging from free to expensive monthly subscriptions. One source of free data is: http://download.geonames.org/export/zip/

    ** Note: Based on a comment from Steve_O, the format of the geonames file recently changed. I have no control over those changes, and am not notified when changes occur. If you have difficulty loading the data from the file in to the table, you may need to investigate the structure of the data to see if anything has changed.

    Once you have downloaded your data, the next step is to import it in to your database. You can use the following script to do it.

    1. If Exists(Select *
    2.           From   Information_Schema.Tables
    3.           Where  Table_Name = 'ZipCodes'
    4.                  And Table_Type = 'Base Table')
    5.   Drop Table ZipCodes
    6.  
    7. CREATE TABLE [dbo].[ZipCodes](
    8. [country code] [varchar](2) NULL,
    9. [postal code] [varchar](20) NULL,
    10. [place name] [varchar](180) NULL,
    11. [admin name1] [varchar](100) NULL,
    12. [admin code1] [varchar](20) NULL,
    13. [admin name2] [varchar](100) NULL,
    14. [admin code2] [varchar](20) NULL,
    15. [admin name3] [varchar](100) NULL,
    16. [admin code3] [varchar](20) NULL,
    17. [latitude] [decimal](8, 4) NULL,
    18. [longitude] [decimal](8, 4) NULL,
    19. [accuracy] [int] NULL
    20. )
    21.  
    22. DECLARE @bulk_cmd varchar(1000)
    23. SET @bulk_cmd = 'BULK INSERT ZipCodes
    24.    FROM ''C:\YourFolder\US.txt''
    25.    WITH (FIELDTERMINATOR=''\t'', ROWTERMINATOR = '''+CHAR(10)+''')'
    26.  
    27. EXEC(@bulk_cmd)
    28.  
    29. Alter Table ZipCodes Drop Column Unused1, Unused2, Unused3
    30.  
    31. CREATE CLUSTERED INDEX IX_ZipCodes_Zip ON dbo.ZipCodes(ZipCode, Longitude, Latitude)
    32. GO
    33. CREATE NONCLUSTERED INDEX IX_ZipCodes_Longitude_Latitude ON dbo.ZipCodes(Longitude,Latitude, ZipCode)
    34.  
    35. GO
    36.  
    37. Select * From ZipCodes

    Next, we’ll need to add a couple of functions that will allow us to use this data.

    The original function that calculates distances has been replaced with this one. There was a flaw in the original version. Based on rounding errors, the calculations fed in to the arc-cosine (acos) function could be greater than 1 or less than -1 which would cause a domain error. To accommodate this flaw, I perform the calculations in multiple steps so that I can catch the values that result in the domain error.

    Credit for finding the flaw goes to Chris. Thank you.

    1. CREATE Function [dbo].[CalculateDistance]
    2.     (@Longitude1 Decimal(8,5),
    3.     @Latitude1   Decimal(8,5),
    4.     @Longitude2  Decimal(8,5),
    5.     @Latitude2   Decimal(8,5))
    6. Returns Float
    7. As
    8. Begin
    9. Declare @Temp Float
    10.  
    11. Set @Temp = sin(@Latitude1/57.2957795130823) * sin(@Latitude2/57.2957795130823) + cos(@Latitude1/57.2957795130823) * cos(@Latitude2/57.2957795130823) * cos(@Longitude2/57.2957795130823 - @Longitude1/57.2957795130823)
    12.  
    13. if @Temp > 1
    14.     Set @Temp = 1
    15. Else If @Temp < -1
    16.     Set @Temp = -1
    17.  
    18. Return (3958.75586574 * acos(@Temp) )
    19.  
    20. End
    1. Create Function [dbo].[LatitudePlusDistance](@StartLatitude Float, @Distance Float) Returns Float
    2. As
    3. Begin
    4.     Return (Select @StartLatitude + Sqrt(@Distance * @Distance / 4766.8999155991))
    5. End
    1. Create Function [dbo].[LongitudePlusDistance]
    2.     (@StartLongitude Float,
    3.     @StartLatitude Float,
    4.     @Distance Float)
    5. Returns Float
    6. AS
    7. Begin
    8.     Return (Select @StartLongitude + Sqrt(@Distance * @Distance / (4784.39411916406 * Cos(2 * @StartLatitude / 114.591559026165) * Cos(2 * @StartLatitude / 114.591559026165))))
    9. End

    Finally, we can begin writing some interesting queries. For example, return a list of zipcodes within 20 miles of zipcode 20013 (Washington, DC).

    1. -- Declare some variables that we will need.
    2. Declare @Longitude Decimal(8,5),
    3.         @Latitude Decimal(8,5),
    4.         @MinLongitude Decimal(8,5),
    5.         @MaxLongitude Decimal(8,5),
    6.         @MinLatitude Decimal(8,5),
    7.         @MaxLatitude Decimal(8,5)
    8.  
    9. -- Get the lat/long for the given zip
    10. Select @Longitude = Longitude,
    11.        @Latitude = Latitude
    12. From   ZipCodes
    13. Where  ZipCode = '20013'
    14.  
    15. -- Calculate the Max Lat/Long
    16. Select @MaxLongitude = dbo.LongitudePlusDistance(@Longitude, @Latitude, 20),
    17.        @MaxLatitude = dbo.LatitudePlusDistance(@Latitude, 20)
    18.  
    19. -- Calculate the min lat/long
    20. Select @MinLatitude = 2 * @Latitude - @MaxLatitude,
    21.        @MinLongitude = 2 * @Longitude - @MaxLongitude
    22.  
    23. -- The query to return all zips within a certain distance
    24. Select ZipCode
    25. From   ZipCodes
    26. Where  Longitude Between @MinLongitude And @MaxLongitude
    27.        And Latitude Between @MinLatitude And @MaxLatitude
    28.        And dbo.CalculateDistance(@Longitude, @Latitude, Longitude, Latitude) <= 20

    You could also use this for a "store locator". On a website, potential customers could enter their zipcode and you could present a list of top 5 closest stores.
    The query shown below assumes you have a Stores table with a ZipCode column.

    1. Declare @Longitude Decimal(8,5)
    2. Declare @Latitude Decimal(8,5)
    3.  
    4. Select  @Longitude = Longitude,
    5.         @Latitude = Latitude
    6. From    ZipCodes
    7. Where   ZipCode = '20013'
    8.  
    9. Select  Top 5 Stores.StoreName, ZipCodes.City,  dbo.CalculateDistance(@Longitude, @Latitude, ZipCodes.Longitude, ZipCodes.Latitude) As Distance
    10. From    @Stores As Stores
    11.         Inner Join ZipCodes
    12.             On Stores.ZipCode = ZipCodes.ZipCode
    13. Order By dbo.CalculateDistance(@Longitude, @Latitude, ZipCodes.Longitude, ZipCodes.Latitude)

    As you can see, these queries perform very well, and are relatively easy to write.

    About the Author

    George has been developing software professionally for 19 years, first for the department of defense, and then for various other companies. In 1998, George started his software company, Orbit Software, specializing in School Bus Transportation software. His specialty is refining SQL Server queries to deliver optimal performance.
    Social SitingsTwitterLTD RSS Feed
    InstapaperVote on HN

    76 comments

    Comment from: Alex Ullrich [Member] Email
    *****
    Alex Ullrich I haven't used your new function (I will have to do another test) but in SQL 2005 you can also use the CLR functions very effectively for calculations like this:

    http://www.alexcuse.com/BlogEntry.aspx?PostID=55

    I'm taking a better look at Denis' 2008 method right now, I want to test all three later. I'd like to take a look at the new function against the other one (which I stole from you as well) and see how they stack up too.
    02/12/09 @ 19:39
    Comment from: SQLDenis [Member] Email
    *****
    SQLDenis Alex maybe you should create a post showing how to do it with the CLR
    02/13/09 @ 04:34
    Comment from: Chris [Visitor] Email
    *****
    Chris Thanks for sharing this. I'm trying it out now.

    I went looking for information on how to calculate distances using longitude and latitude using kilometers and came across this page: http://www.meridianworlddata.com/Distance-Calculation.asp

    On there they list the following values that could be substituted for the first value in your CalculateDistance function:
    3437.74677 (nautical miles)
    6378.7 (kilometers)
    3963.0 (statute miles)

    You used a value of 3958.75586574 - how did you arrive at your value?

    I might suggest adding another input to the CalculateDistance function to allow for results to be generated in different units of measure (miles vs. kilometers). I've done the same here but thought others could benefit from the same.

    Thanks again.
    02/13/09 @ 08:35
    Comment from: Alex Ullrich [Member] Email
    Alex Ullrich Denis, its' in the link. I guess I could post it here too but that would not be very DRY of me ;)

    Actually it could be helpful for others to have all three in one place (then we could do a wrap-up post testing them all). I'll revisit the CLR version when I get a chance, or if I'm lazy just take the relevant bits out of that post.

    Expect to see it next week. I'll stop rambling now!
    02/13/09 @ 09:44
    Comment from: Chris [Visitor] Email
    *****
    Chris I worked with the code today and have a few more thoughts:

    I downloaded the source zip code data using the link you provided. You should know that there are a small number of instances when a single zipcode has multiple rows in the table. I believe this happens when a zipcode spans multiple cities. For my uses I’m picking the first record from each zipcode so as not to duplicate my results.

    I did run into some errors:
    Trying to calculate the distance between ZipCode 11779 and itself throws the following error in SQL Server 2000:
    “A domain error occurred.”
    This doesn’t happen for all zipcodes, though. Most seem to produce a very small distance difference and round out to 0. Handling matching zipcodes up-front would take care of this. I’ve also seen cases where two zip codes have the same coordinates (e.g., 72035 and 72033) and this can cause the same error.
    02/13/09 @ 16:17
    Comment from: George Mastros (gmmastros) [Member]
    George Mastros (gmmastros) Chris,

    It well known that some zip codes span multiple cities/towns. It's also well known that some cities have multiple zip codes.

    I figured out what causes the domain error. The CalculateDistance function uses the arc-cosine (acos) function. It's well documented in books on line that acos returns a domain error if you supply a value less than -1 or greater than +1. Identification is half the battle, right. I will post back again with a solution to this problem.

    Thank you very much for bringing this to my attention.
    02/13/09 @ 16:35
    Comment from: George Mastros (gmmastros) [Member]
    George Mastros (gmmastros) Chris,

    I modified the function to accommodate this problem. I edited the blog to replace the flawed version of the function.

    Thanks again for pointing out the problem.
    02/13/09 @ 16:49
    Comment from: Dale R. Whitaker [Visitor] Email
    *****
    Dale R. Whitaker I like this and admire what you shared with others.
    02/15/09 @ 06:09
    Comment from: Doug [Visitor]
    ****-
    Doug Since @Distance is squared, how does one ask for a logitude 5 miles less than the one specified?
    It seems like the distance calculations are not valid fro negative numbers, true?
    03/31/09 @ 12:30
    Comment from: JayZ [Visitor]
    JayZ Very nice work, you saved me a lot of time.

    Thanks you!
    05/05/09 @ 08:57
    Comment from: Henryc [Visitor]
    *****
    Henryc Hello all,
    Nice work. We are working on an application where
    2 zip codes are entered in the same query and we want to have the option of varying the mileage radii around each zip code entered and we need to return a list of all the zip code values found in each radius set.
    For example, enter 30301 for origin and 50008 for destination and 20 mile radius around origin and 25 mile radius around the destination. Each record set stored in the database will contain origin and destiation zip codes. The table of zip code values will be compared to zip code table and any records where the origin and destination zip codes in the stored table that are included in the 2 sets will return these records, others will be excluded. Can you give any suggestions of the best way to construct this?
    Thanks
    05/14/09 @ 21:40
    Comment from: rod marsh [Visitor]
    *****
    rod marsh Great work. Thank you very much. Just incorporated this into a complicated search query and it worked a treat. A++
    05/19/09 @ 03:39
    Comment from: Smoker [Visitor]
    *****
    Smoker Can Someone tell me what's the exact formula to calculate maximum latitude
    and longitude
    06/30/09 @ 23:34
    Comment from: George Mastros (gmmastros) [Member]
    George Mastros (gmmastros) @Smoker,

    Your question confuses me. Normally, Longitude has a range of -180 to +180 and Latitude has a range -90 to 90.

    Since these are constants, no formula is needed.

    I suspect your question is more complicated than this. As such, I encourage you to post a question on our forum, and I will be glad to help you out. Please post your question here:

    http://forum.lessthandot.com/viewforum.php?f=17
    07/01/09 @ 05:37
    Comment from: Smoker [Visitor]
    Smoker @George

    Thanks for quick reply. I understand the range values but I want to know how
    you have calculated the values like 4784.39411916406 , 114.591559026165 ,
    4766.8999155991 in the LatitudePlusDistance and LongitudePlusDistance.
    07/01/09 @ 08:56
    Comment from: Maria [Visitor]
    *****
    Maria Hi George,

    I was looking for the same kind of proximity search code base and found it here.
    Great Work. But In Functions LatitudePlusDistance and LongitudePlusDistance
    some Precalculated values are used like 4784.39411916406 , 114.591559026165 ,
    ,4766.8999155991 which I am unable to understand. Can you please tell us how you have calculated those values

    Thanks In Advance,
    Maria
    07/02/09 @ 20:35
    Comment from: Kris Boldt [Visitor] · http://www.naturalsolutionsmag.com
    *****
    Kris Boldt Thanks for the code. It worked perfectly.
    I was looking for a solution for this type of function and was having a hard time until I found this post.

    Great job George!

    07/30/09 @ 15:51
    Comment from: John [Visitor]
    *****
    John This is great! Thanks so much for sharing.
    08/01/09 @ 14:53
    Comment from: Lou [Visitor]
    *****
    Lou I cannot thank you enough for posting this...I'm working on an iPhone app to go along with an ASP-driven site that we have and I had no idea how to do this until I was directed to this article.
    08/29/09 @ 09:36
    Comment from: Michael [Visitor]
    *****
    Michael I used your setup and things for the most part appear the way they should. However, I have several that don't seem to add up.

    In comparing the results with Google Maps, some locations are off by a factor of 7.

    For instance: http://www.google.com/maps?f=d&source=s%5Fd&saddr=10715+Downsville+Pike+Ste+100+MD+21740&daddr=444+East+College+Ave+Ste+120+State+College+PA,+16801&geocode=&hl=en&mra=ls&sll=40.799159,-77.856052&sspn=0.008836,0.01826&g=444+East+College+Ave+Ste+120+State+College+PA,+16801&ie=UTF8&t=h&z=8

    Which uses these coordinates:
    long/lat
    40.799159,-77.856052
    40.22018,-78.140205

    Google says the distance (driving) is 144 miles vs the query saying ~21 miles (as the crow flies).

    I've looked up other people's implementations and their calculations are practically the same. Such as: http://www.movable-type.co.uk/scripts/latlong.html

    Any ideas?
    10/21/09 @ 20:11
    Comment from: George Mastros (gmmastros) [Member]
    George Mastros (gmmastros) Michael,

    The calculate distance function I present here accepts 4 parameters. It's important that you send the data in to the function in the correct order. Using your coordinates, if you pass lat,long,lat,long you will get 21 miles. If you pass long,lat,long,lat (the correct order of the parameters) you get 42.7 miles.

    Using Google Earth (it has a straight line ruler tool), the distance between your coordinates is reported as 42.61. The difference between Google Earth and my calculations is approximate 475 feet over the course of 42 miles (or 0.2% error).

    By the way, your coordinates are wrong too. They should be:
    40.798173,-77.856637
    39.607699,-77.753558

    My distance calculation formula reports this as 82.4 miles (crow fly). Google earth reports 82.2 miles. Following a street network, Google earth reports 144 miles.
    10/22/09 @ 06:40
    Comment from: Dhirendra Singh [Visitor]
    Dhirendra Singh Great job George!
    Thanks for the code. It worked perfectly.
    10/26/09 @ 04:15
    Comment from: Erik [Member] Email
    Erik For those who have been asking, the various constants used in George's distance calculation come from:
    - Conversions to make different units of measure work: kilometers, miles, etc.
    - The nonuniform, ovoid shape of the Earth requiring different circumferences for equatorial vs. meridional calculations.
    - The particular Earth geometry system used. Some of these are NAD 27, NAD 83, WGS 84--see http://www.ngs.noaa.gov/faq.shtml .
    12/14/09 @ 17:13
    Comment from: Naomi Nosonovsky [Member]
    Naomi Nosonovsky George,

    Why you're using FLOAT for the @Temp?
    04/13/10 @ 10:54
    Comment from: shankar [Visitor]
    shankar Thanks for this script. Helped us.
    06/21/10 @ 01:31
    Comment from: Kevin [Visitor]
    Kevin I am having an issue during the import process. It seems that some of the data cannot be converted to Decimal(8, 5) and the bulk import fails because of this in the Longitude column

    Any thoughts?
    07/08/10 @ 08:49
    Comment from: George Mastros (gmmastros) [Member]
    George Mastros (gmmastros) Kevin, can you tell me what you used for the source of your data so that I can (hopefully) reproduce the problem and fix it?
    07/08/10 @ 09:19
    Comment from: Pollirrata [Visitor] Email
    Pollirrata Great work!! It's really nice having something like this...

    @Kevin
    I faced some the same problem inserting Mexico's data 'cause the order of columns in txt file was different from script and our state abbreviations can be up to 4 characters... Just checking the file content worked for me "fixing" this issue... maybe this could help you....
    07/08/10 @ 13:38
    Comment from: RoyHink [Visitor]
    RoyHink Great work, and Thank You for posting this!

    I am a web coder (PHP/JS/Python) and know just enough SQL to do Selects, Updates and Joins.

    I apologize upfront if I'm missing something basic and commonly understood!

    I am running MSSQL 2000 on a WIN2003 R2 server

    This code is throwing the following errors in Query Analyzer:

    Server: Msg 156, Level 15, State 1, Procedure CalculateDistance, Line 21
    Incorrect syntax near the keyword 'CREATE'.
    Server: Msg 111, Level 15, State 1, Procedure CalculateDistance, Line 21
    'CREATE FUNCTION' must be the first statement in a query batch.
    Server: Msg 137, Level 15, State 1, Procedure CalculateDistance, Line 24
    Must declare the variable '@StartLatitude'.
    Server: Msg 111, Level 15, State 1, Procedure CalculateDistance, Line 26
    'CREATE FUNCTION' must be the first statement in a query batch.
    Server: Msg 137, Level 15, State 1, Procedure CalculateDistance, Line 33
    Must declare the variable '@StartLongitude'.

    Would you be willing to give me some direction?

    Thanks
    07/30/10 @ 11:46
    Comment from: SQLDenis [Member] Email
    SQLDenis Roy, you need to either run each create function/create proc by itself or you need to put GO after the code block


    CREATE FUNCTION [dbo].[LatitudePlusDistance](@StartLatitude FLOAT, @Distance FLOAT) RETURNS FLOAT
    AS
    BEGIN
    RETURN (SELECT @StartLatitude + SQRT(@Distance * @Distance / 4766.8999155991))
    END
    GO -- -- add this

    CREATE FUNCTION [dbo].[LongitudePlusDistance]
    (@StartLongitude FLOAT,
    @StartLatitude FLOAT,
    @Distance FLOAT)
    RETURNS FLOAT
    AS
    BEGIN
    RETURN (SELECT @StartLongitude + SQRT(@Distance * @Distance / (4784.39411916406 * COS(2 * @StartLatitude / 114.591559026165) * COS(2 * @StartLatitude / 114.591559026165))))
    END
    07/30/10 @ 13:17
    Comment from: George Mastros (gmmastros) [Member]
    George Mastros (gmmastros) Thanks for posting Denis. This is exactly what I would have suggested.
    07/30/10 @ 14:10
    Comment from: RoyHink [Visitor]
    RoyHink Thanks, SQLDenis, that worked perfectly.
    07/30/10 @ 15:31
    Comment from: Mike Payne [Visitor] Email
    Mike Payne I was having a problem (Domain Error) with my own query and ran into this site trying to fix it.

    I definitely like this method better.

    However, it does not return consistent results as it sits.

    I noticed that a comparison of the two queries (on a zip code not producing the domain error) returned slightly different result sets.

    Even though one zip code was coming back within 4.7 miles in a 5 mile radius search - this query was not returning it.

    I ended up multiplying the Distance by 1.1 and restricting the results without the addition of .1

    This returned identical results. I think that this is a problem within the MAX/MIN Lat/Long functions.

    This "quick fix" returned the right results for me - but I figured I would let you know, in case you wanted to research it further.

    Thanks for the query either way.
    08/03/10 @ 14:17
    Comment from: ashwani01 [Member] Email
    ashwani01 IT Professionals are responsible for creating new technology for development.
    08/05/10 @ 01:25
    Comment from: David Lavoie [Visitor]
    David Lavoie Someone could telle me wich values can I use in LatitudePlusDistance and LongitudePlusDistance for calculation with Km?
    09/27/10 @ 14:41
    Comment from: George Mastros (gmmastros) [Member]
    George Mastros (gmmastros) David Lavoie,

    I would recommend that you allow the calculations to process the data using miles. If you want the results in KM, simply multiply values by 1.609344. You could modify the CalculateDistance function to multiply by this value just prior to returning.
    09/27/10 @ 15:34
    Comment from: Thanks [Visitor]
    Thanks I rarely comment on posts but yours has really made life easier. Thank you for posting this.
    02/12/11 @ 16:40
    Comment from: Tom [Visitor] · http://zipcodes.qpzm.com
    Tom This is fantastic, it is fast. So much faster than using a prepopulated distance table of millions or rows.

    Awesome, thank you.
    06/20/11 @ 16:01
    Comment from: Talib [Visitor] Email
    Talib The idea was really nice and save my time, Excelent!

    i was calculating the distance differently which was causing delay when i apply the functions on a 2 million table but this has really improved the performance!

    thanks agains.
    08/08/11 @ 03:43
    Comment from: Matt [Visitor]
    Matt George,

    Thank you so much for this code. I would like to know the reason why you used those values to divide and multiply by. I mean some documentation/site for the values used in all functions.

    In fact a few other users asked the same question, but would be very helpful if you could provide some answers.

    Thank you so much.
    10/04/11 @ 07:47
    Comment from: George Mastros (gmmastros) [Member]
    George Mastros (gmmastros) Matt,

    The constants I use in the calculations exist because I originally used a different formula, and then reworked it in to this format.
    10/04/11 @ 08:49
    Comment from: Matt [Visitor]
    Matt Thanks George.

    I mean is it part of any formula (like haversine formula)? What is the formula you have used here?

    What is the reason to use these values
    57.2957795130823
    3958.75586574
    4766.8999155991

    The reason i ask is because, if i use them in my code, i need to document them and provide a reason to my managers as to why these constants are used. I can provide a link to your site, but the site does not answer these queries.

    Any formula/documentayion/site would be a great help.
    10/04/11 @ 09:07
    Comment from: Matt [Visitor]
    Matt Hi George,

    When a zipcode spans multiple cities, then the below query

    SELECT @Longitude = Longitude,
    @Latitude = Latitude
    FROM ZipCodes
    WHERE ZipCode = '20013'

    takes longer to execute if the zipcodes table is large and returns the last row. Is it beneficial to use the min/max values instead?

    What would you suggest?

    Matt
    10/19/11 @ 07:16
    Comment from: Naomi Nosonovsky [Member]
    Naomi Nosonovsky George,

    Can you please explain each constant you used and the formula used to calculate the bounding box?

    Thanks in advance.
    --------------
    Also, assuming we pass distance in km, how the formulas should be adjusted? E.g. how to find all rows in the database that are in the radius of 25 km from the passed latitude and longitude?
    11/22/11 @ 11:38
    Comment from: George Mastros (gmmastros) [Member]
    George Mastros (gmmastros) Naomi,

    I started with a function I found on the internet about 10 years ago. Originally it was a multi-step function which I converted to a single formula.

    The conversion between miles and kilometers is well known. All you need to do is multiply distance by 0.621371192 to convert to kilometers. In fact, every place you see @Distance, replace with (@Distance * 0.621371192).
    11/22/11 @ 12:01
    Comment from: Naomi Nosonovsky [Member]
    Naomi Nosonovsky In the CalculateDistance the RETURN (3958.75586574 * ACOS(@Temp) ) first number is the Earth Radius in miles.

    I assume we need this radius in km for kilometers.

    In the LatitudePlusDistance and the other function I don't understand where all these constants come from and what are they. If we passed our distance in km, how these two formulas need to be adjusted?

    Can you please post a section for km calculations?
    11/22/11 @ 12:29
    Comment from: Naomi Nosonovsky [Member]
    Naomi Nosonovsky Also, the coefficient is 1.609344 from miles to km.
    11/22/11 @ 13:10
    Comment from: Brenda [Visitor]
    Brenda George,

    Thank you so much for this code. It has greatly helped me.

    This is what i did for KM conversion. All i did is, write a CalculateDistanceInKms udf and in the last line, did as you suggested in one of your earlier posts.

    RETURN (1.609344 * ACOS(@Temp))

    Please let me know if this is correct.

    Brenda
    11/24/11 @ 07:17
    Comment from: Tony [Visitor]
    Tony Works great, thanks for saving me about a week!
    03/19/12 @ 17:46
    Comment from: DrDomski [Visitor]
    DrDomski Props for sharing this with the community. Thank you.
    03/28/12 @ 18:43
    Comment from: Steve_O [Visitor] Email
    Steve_O I must be missing something on the first piece. I've downloaded the US zip file & am attempting to bulk insert it into SQL 2008 R2.

    I'm using SQL Server Express 2008 R2, and the table will create okay, but the Bulk Insert keeps failing.

    I get several error messages. This one is repeated once for each of the first 10 rows of the text file.
    Msg 4863, Level 16, State 1, Line 1
    Bulk load data conversion error (truncation) for row 1, column 1 (Country).

    Then, I get these two errors:
    Msg 7399, Level 16, State 1, Line 1
    The OLE DB provider "BULK" for linked server "(null)" reported an error. The provider did not give any information about the error.
    Msg 7330, Level 16, State 2, Line 1
    Cannot fetch a row from OLE DB provider "BULK" for linked server "(null)".

    Can this be bulk inserted into a SQL EXPRESS 2008 R2 database? Or is it something else?

    04/11/12 @ 14:19
    Comment from: George Mastros (gmmastros) [Member]
    George Mastros (gmmastros) @Steve_O

    I haven't downloaded a file in a really long time. It's possible that geonames.org has changed the format of the file. I would encourage you to open the file using an ASCII editor (like notepad) and examining the columns. You may need to make adjustments to the structure of the table you are importing the data in to.
    04/11/12 @ 14:48
    Comment from: Steve_O [Visitor] Email
    Steve_O George - thanks for the reply. Geonames.org HAD changed the format of the file. This structure now works for US (testing now with others).

    [geonameID] int NULL,
    [name] varchar(200) NULL,
    [ASCIIName] varchar(200) NULL,
    [AlternateNames] varchar(5000) NULL,
    [latitude] decimal(8,5) NULL,
    [longitude] decimal(8,5) NULL,
    [FeatureClass] char(1) NULL,
    [FeatureCode] varchar(10) NULL,
    [CountryCode] varchar(2) NULL,
    [AlternateCountryCode] varchar(60) NULL,
    [FIPSCode] varchar(20) NULL,
    [CountyCode] varchar(80) NULL,
    [Admin3Code] varchar(20) NULL,
    [Admin4Code] varchar(20) NULL,
    [population] bigint NULL,
    [elevation_meters] int NULL,
    [DigitalElevationMdl] int NULL,
    [TimeZone] varchar(40) NULL,
    [ModDate] varchar(10) NULL

    However, not ALL countries are in the above format. FYI, the AlternateNames above must be changed from varchar(5000) to varchar(MAX) to prevent truncation & to load for AllCountries.

    Some countries are in this format:
    [CountryCode] [varchar](2) NULL,
    [ZipCode] [varchar](5) NULL,
    [PostalCode] varchar(20) NULL,
    [City] varchar(180) NULL,
    [subdivision_StateName] varchar(100) NULL,
    [subdivision_StateCode] varchar(20) NULL,
    [subdivision_CountyProvinceName] varchar(100) NULL,
    [subdivision_CountyProvinceCode] varchar (20) NULL,
    [subdivision_CommunityName] varchar (100) NULL,
    [subdivision_CommunityCode] varchar (20) NULL,
    [latitude] decimal(8,5) NULL,
    [longitude] decimal(8,5) NULL,
    [accuracy] varchar(1) NULL

    I haven't looked to see why some files are in one format vs another, but this was my issue. If you can edit the article, it might be wise to just add the caveat to verify the file structure. Thanks for the help, and for the GREAT utility.
    04/12/12 @ 09:14
    Comment from: George Mastros (gmmastros) [Member]
    George Mastros (gmmastros) @Steve_O,

    Thank you for investigating this issue, and also for posting the corrected table structure. I have updated the article as you suggested and also gave you credit for bringing this to my attention.

    I'm glad you found this useful.
    04/12/12 @ 09:27
    Comment from: Jim Schell [Visitor]
    Jim Schell George,

    Question from a noob here. I follow your function, some of the math is a little over my head to say the least.

    WHat I am curious about is how to take the dbo.calculatedistance value and convert it to feet.

    I have to hard code the distance value of say 500 feet which is .094696 feet, what I get back in the calculatedistance function used as part of select is all decimal as expected. How can we convert this to feet on the fly?

    Case study is I need to know every hydrant within 500 feet of a specific lat long and part of the recordset should the actual distance because not every hydrant is actually 500 feet away.

    Hope this make sense?

    Thanks

    Jim
    04/24/12 @ 14:04
    Comment from: George Mastros (gmmastros) [Member]
    George Mastros (gmmastros) Jim,

    The CalculateDistance function returns Miles. You could modify the function to multiply by 5280 to convert to feet. Change the last line to:

    Return (5280 * 3958.75586574 * acos(@Temp) )

    I have some concerns about your approach for the case study. The premise for this is to calculate straight line distances (crow fly). Does your 500 feet requirement represent crow fly distances or is it road distances. For example, a hydrant could be around the corner and could be within 500 feet as the crow flies, but actually be 700 feet if you restrict your movement to following the roads.
    04/24/12 @ 14:36
    Comment from: Jim Schell [Visitor] Email
    Jim Schell George,

    Thanks for the response!!!

    I used this before I saw your response:

    ROUND(dbo.CalculateDistance(@Longitude, @Latitude, Longtitude, Latitude)* 5280,0) AS Hyd_Dist

    as a field being returned giving me the number of feet the hydrant is based on the lat/long of the hydrant and the lat/long of the building in question. Obviously I have zero control over where google says the centroid point is that they say the address belongs to.

    The 500 feet was an arbitrary number for testing. Different jurisdictions have different requirements, some 300 some 250 etc, but you are correct it is as the crow flies and not by road segment. I have zero access to a road segment table and quite frankly it would be over my head for sure.

    The end result here is to pull up a crystal report with a googlemap or similar type map in the report and populate the hydrants and the address with color coded push pin type markers indicating the house and the gpm (gallons per minute) flow of the hydrant.

    The plans examiner would click a command button inside an app open a the crystal report giving him/her the birds eye view of the building and the hydrants near by. Giving them visual information about where the nearest hydrants are in relation to the building they are looking at.

    Sorry the long response, hoefully this make sense now?

    Thanks

    Jim
    04/25/12 @ 07:08
    Comment from: George Mastros (gmmastros) [Member]
    George Mastros (gmmastros) Jim,

    Thanks for the feedback.

    I've written similar functionality for students walking to a bus stop. I needed to use actual road distances, which makes the process considerably more difficult.

    I'm glad you got this working.
    04/25/12 @ 09:40
    Comment from: Jim Schell [Visitor]
    Jim Schell Yes it would and not having access to a road segment table let alone trying to tie that together would be a nightmare!
    04/25/12 @ 16:12
    Comment from: Jim Schell [Visitor] Email
    Jim Schell George et al,

    Here is the 64 dollar question. How can we determine the cardinal compass point between two lat long points,

    latlongA is NEWS... of latlongB I can only see the need for 8 compass points but there could easily be ENE, NE or NNE etc.

    This is a spin on the locate 5 occuancies from my position 2 are NE of me,1 is W and the other 2 are S of me.

    Make Sense?
    04/26/12 @ 15:32
    Comment from: George Mastros (gmmastros) [Member]
    George Mastros (gmmastros) Jim,

    Calculating the angle between coordinates is tricky because the coordinates we are talking about here (Latitude/Longitude) have values represented in degrees. The problem is that you cannot directly use the Lat/Long values because a degree change in latitude is shorter at the equator than a change of 1 degree near the north or south pole.

    If I get a chance, I'll see if I can work something up for this.
    04/26/12 @ 17:16
    Comment from: Jim Schell [Visitor] Email
    Jim Schell George,

    I morphed this code with yours to kitbash something that would work. THE NE, NW, SW & SE char return does not work, logic error somewhere in there...

    ALTER FUNCTION [dbo].[CalculateBearing]
    (@Longitude1 DECIMAL(10,6),
    @Latitude1 DECIMAL(10,6),
    @Longitude2 DECIMAL(10,6),
    @Latitude2 DECIMAL(10,6))
    RETURNS VARCHAR
    AS
    BEGIN
    DECLARE @Cardinal CHAR (8)
    DECLARE @Bearing DECIMAL(10,6)
    DECLARE @x DECIMAL(10,6)
    DECLARE @y DECIMAL(10,6)
    DECLARE @dLat DECIMAL(10,6)
    DECLARE @dLon DECIMAL(10,6)
    DECLARE @rLat1 DECIMAL(10,6)
    DECLARE @rLat2 DECIMAL(10,6)

    IF @Longitude1 IS NULL OR @Longitude2 IS NULL OR @Latitude1 IS NULL OR @Latitude2 IS NULL
    SET @Bearing = NULL
    ELSE
    BEGIN
    SET @dlat = Radians(@Latitude2 - @Latitude1)
    SET @dlon = Radians(@Longitude2 - @Longitude1)
    SET @rLat1 = Radians(@Latitude1)
    SET @rLat2 = Radians(@Latitude2)
    SET @y = sin(@dlon)*cos(@rLat2)
    SET @x = cos(@rLat1)*sin(@rLat2)-sin(@rLat1)*cos(@rlat2)*cos(@dlon)
    IF (@x = 0 and @y = 0)
    SET @Bearing = null
    ELSE
    BEGIN
    SET @Bearing = cast((Degrees(atn2(@y,@x)) + 360) as decimal(10,6)) % 360
    END
    END
    IF @Bearing GT 0 and @Bearing LT 22.6 SET @Cardinal = 'N'
    IF @Bearing GT 22.5 and @Bearing LT 67.6 SET @Cardinal = 'NE'
    IF @Bearing GT 67.5 and @Bearing LT 112.6 SET @Cardinal = 'E'
    IF @Bearing GT 112.5 and @Bearing LT 157.6 SET @Cardinal = 'SE'
    IF @Bearing GT 157.5 and @Bearing LT 202.6 SET @Cardinal = 'S'
    IF @Bearing GT 202.5 and @Bearing LT 247.6 SET @Cardinal = 'SW'
    IF @Bearing GT 247.5 and @Bearing LT 292.6 SET @Cardinal = 'W'
    IF @Bearing GT 292.5 and @Bearing LT 337.6 SET @Cardinal = 'NW'
    IF @Bearing GT 337.5 and @Bearing LTE 360.0 SET @Cardinal = 'N'

    -- Return the result of the function
    RETURN @Cardinal
    END
    GO


    The original version of this code returned a float datatype of the bearing in degrees.

    Jim
    04/26/12 @ 22:32
    Comment from: Jim Schell [Visitor] Email
    Jim Schell George et al,

    The formatting did not stick for my previous post.

    Ugh, how dow I fix that?

    Jim
    04/27/12 @ 06:05
    Comment from: Paul [Visitor]
    Paul Hi George,

    It looks like they changed the format of the data file again. I looked at the readme and it gives a completely different list of fields. I am not sure which ones to use now. Any help would be appreciated as this would be perfect if I can get it working.

    Also, the above code references a column called Zipecode when there is no such column in the create table statement. Unless I am missing something...

    Paul
    05/09/12 @ 12:34
    Comment from: Paul [Visitor]
    Paul Good morning George,

    Well, I managed to get it all working. I went in and changed all the field names. The following create table worked for me.

    CREATE TABLE [dbo].[ZipCodes](
    [country code] [varchar](2) NULL,
    [postal code] [varchar](20) NULL,
    [place name] [varchar](180) NULL,
    [admin name1] [varchar](100) NULL,
    [admin code1] [varchar](20) NULL,
    [admin name2] [varchar](100) NULL,
    [admin code2] [varchar](20) NULL,
    [admin name3] [varchar](100) NULL,
    [admin code3] [varchar](20) NULL,
    [latitude] [decimal](8, 4) NULL,
    [longitude] [decimal](8, 4) NULL,
    [accuracy] [int] NULL
    )


    I created all of the funtions and it works perfectly. Here is my next question. How do I access these funtions in Access? The company I work for has a set of legacy Access front ends that use my SQL DB as its data source. Can I simply write your funtions in Access? If so, is there anything I should look for when changing the format? I have already linked at local table to the SQL table with no problems...

    Thank you for any help..

    Paul
    05/10/12 @ 07:45
    Comment from: George Mastros (gmmastros) [Member]
    George Mastros (gmmastros) Paul,

    Thanks for the feedback. I've been swamped at my "paying" job and haven't had a chance to look at this. You should be able to create the functions within SQL and then call them using Access as the front end application.

    I haven't used Access in a really long time, so I am probably not the best person to give advice on this. I do know an Access expert and will try to get that person to look at your question and provide an answer.
    05/10/12 @ 09:33
    Comment from: remou [Member] Email
    remou Hi Paul, here are a few rough ideas in case you have not got around to figuring it out yet. You can refer to a function by name in a passthrough query (http://support.microsoft.com/kb/303968), so, for example,

    SELECT dbo.CalculateDistance
    (-114.0812,51.0171,-116.0812,52.0171) AS Distance

    And in VBA:

    Dim rs As New ADODB.Recordset
    Dim cn As New ADODB.Connection

    Long1 = -114.0812
    Lat1 = 51.0171
    Long2 = -114.0412
    Lat2 = 51.0571
    cn.Open scon

    rs.Open "SELECT dbo.CalculateDistance(" _
    & Long1 & "," & Lat1 _
    & "," & Long2 & "," & Lat2 & ") AS Distance", cn
    Debug.Print rs!distance

    And so forth.

    I hope I have not missed your point.
    05/11/12 @ 06:58
    Comment from: Paul [Visitor]
    Paul Hi remou,

    thanks for your suggestions. I will give them a try and report back...

    Presumably, I would put the VBA code in the form I want to results to show up in?

    Paul
    05/14/12 @ 12:29
    Comment from: remou [Member] Email
    remou Hi Paul
    You can create a function in any module and feed in the necessary latitude and longitude, or you can put it in the code behind a form and refer to various text boxes on the form.
    07/04/12 @ 10:39
    Comment from: Garrett [Visitor] Email
    Garrett Great post. One question though. I'm trying to get store locater values back for both U.S. and Canada. This process works fine when they are done individually but when I enter a Canadian Postal Code of V5K0A1 (Vancouver) it is telling me that a store located in 98055 (Renton, WA) is thousands of miles away. Any ideas?
    02/21/13 @ 11:00
    Comment from: George Mastros (gmmastros) [Member]
    George Mastros (gmmastros) Garrett,

    Can you share the latitude/longitude values you are using for your store located in Renton, WA, and the Latitude/Longitude value you are using for Canadian postal code V5K0A1 ?
    02/21/13 @ 11:05
    Comment from: Garrett [Visitor] Email
    Garrett Well shoot! I didn't even think to look at the raw database values. Duh! That appears to be my problem. Apparently the download I received has them incorrect. I will go out to the site you listed and pull them in from there, but here are the values.
    V5K0A1
    Lat: 49.2889
    Long: -23.036

    98055
    Lat: 47.4717
    Long: -122.1915

    Thanks for pointing out the obvious as I seem to have overlooked it. It always comes back to the data.
    02/21/13 @ 12:54
    Comment from: George Mastros (gmmastros) [Member]
    George Mastros (gmmastros) Garrett,

    Been there, done that. Data is the lifeblood of any application.

    The difference here is that I have tons of experience with this user defined function, and therefore I have complete faith that it works properly and returns an accurate result. You don't have that experience yet, which is probably why I immediately thought of the data, and you didn't.

    Best of luck with your project.
    02/21/13 @ 13:04
    Comment from: Al [Visitor]
    Al This is really helpful. I believe its been asked, but I can't seem to find the answer. Can you explain what the following constants in your functions refer to:

    LatitudePlusDistance: 4766.8999155991

    LongitudePlusDistance: 4784.39411916406 and 114.591559026165

    Thanks!

    For anyone else interested, the values in the distace calculator are 3958.75586574 (radius of Earth in miles) and 57.2957795130823 (degree to radian conversion factor)

    02/21/13 @ 13:04
    Comment from: George Mastros (gmmastros) [Member]
    George Mastros (gmmastros) Al,

    Those constants were derived by taking the distance formula, making the distance a variable, and solving for Latitude (and then again for Longitude).

    02/21/13 @ 13:11
    Comment from: Jamie Rytlewski [Visitor] · http://www.jamier.net
    Jamie Rytlewski Thanks for the post. I'd like to mention that the file with the Latitude/Longitude had some inaccurate Lat/Lon. I created a console app that will update the lat/lon from Bing Maps if anyone is interested.

    Where I found the problem was with two zip codes that are next to each other, but were considered 7 miles apart with the download. I checked the values in Bing and Google and found out that the lat/lon was incorrect.
    04/24/13 @ 06:26

    Leave a comment


    Your email address will not be revealed on this site.

    To mislead the spambots.

    Your URL will be displayed.
    (Line breaks become <br />)
    (Name, email & website)
    (Allow users to contact you through a message form (your email will not be revealed.)