Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Jan Skovgaard 11280 posts 23678 karma points MVP 11x admin c-trib
    Mar 02, 2015 @ 19:03
    Jan Skovgaard
    0

    Find top 10 nearest locations from DB near known location

    Hey guys

    This questions is not directly Umbraco related but I'm going to ask it in here anyway hoping I can get some tips on how to best achieve my goal.

    Ok...so I'm working on a pet project where I have a known location for a venue on my model. Based on the latitude and longitude of this venue I want to list the top 10 of the nearest restaurants.

    I have the latitude/longitude for the venue on the Model in Umbraco but want to fetch the restaurants from a custom restaurant table, which is stored in the Umbraco database. The table contains more than 100.000 restaurants.

    I have been trying to Google how to do this but I'm a bit confused about how to handle the calculations of the distance and whether I should do it directly in the database using a stored procedure and how to make the search performant - How can one enable caching for this? And should I make a surface controller for the functionality etc?

    I hope my question makes sense - Otherwise I'm happy to provide further details if needed.

    In short...I know point A and want to display the 10 nearest locations based on that known coordinate.

    Looking forward to receive any tips, suggestions and hints.

    /Jan

  • Comment author was deleted

    Mar 02, 2015 @ 19:40

    Are you using at least sql server 2008? Then you should look at using https://technet.microsoft.com/en-us/library/cc280766(v=sql.100).aspx ;The geography data type

  • Comment author was deleted

    Mar 02, 2015 @ 19:45

    fyi this is usually referred to as nearest neighbor search so if you google for that

  • Jan Skovgaard 11280 posts 23678 karma points MVP 11x admin c-trib
    Mar 02, 2015 @ 19:54
    Jan Skovgaard
    0

    Hi Tim

    Thank you for the suggestions - Yes I'm using 2008...Express on local machine though but that should not matter, right?

    Is the geography datatype supported in newer version of MSSQL as well? Think I read somewhere to avoid using it. But all coordinates are currently saved as decimals...does that matter any?

    Aaaah, nearest neighbor! I'm going to google a bit more then :)

    /Jan

  • Mike Chambers 636 posts 1253 karma points c-trib
    Mar 02, 2015 @ 21:23
    Mike Chambers
    0

    If you were looking at google again.. here's a code sample I have from my bookmark using polylines and as an aside measuring distances. Which you could abstract out and use to get a cached distance calc?

    http://www.geocodezip.com/v3_SO_drawLineFroGeocodedResults.html

  • Jan Skovgaard 11280 posts 23678 karma points MVP 11x admin c-trib
    Mar 02, 2015 @ 21:26
    Jan Skovgaard
    0

    Hi Mike

    Thanks for the suggestion but that's not quite what I need I think? :)

    The thing is that I have a table in my database where I need to make the lookup and return some informations like distance in KM/Miles, Name of the restaurant, stars and address for instance.

    I'm still quite puzzled about how to make it happen - Afterall I'm primarily a frontend developer :D

    /Jan

  • Mike Chambers 636 posts 1253 karma points c-trib
    Mar 03, 2015 @ 10:22
    Mike Chambers
    0

    If all your locations are already in the db.. you could write a one off query to compare all the locations for distance via google geocoding (to get the lat/lang positions and then calc route lengths) and then store in a lookup table. Then each publish of a new location do the same for that location vs all the others, maintaining your lookup. Maybe even using http://ucomponents.org/data-types/textstring-array/ as a storage mechanism native to umbraco rather than separate db tables? And you could have this against a location... so it knows all the other places distances from itself... or as a overrriding all locations information??

    I don't think you are likely to be able to have a real time calculation, google maps api throttles api lookups to ~1per second and 2000 a day for the geocoding from my experience.

     

  • Jan Skovgaard 11280 posts 23678 karma points MVP 11x admin c-trib
    Mar 03, 2015 @ 14:15
    Jan Skovgaard
    0

    Hi Mike

    I appreciate the suggestions - But I have several different venues - Each venue will be displayed on it's own page. Since there are more than 100.000 restaurants, I don't want to manage those inside Umbraco. Hence it's own table in the database. I might consider making it a custom section but I won't create the data in Umbraco.

    All I need to do is to feed the known coordinate to my own method and have the 5-10 nearest returned. I tried looking at the google distance matrix stuff but it's prohibited to use if data is not going to be displayed on a map. This data is maybe only going to be displayed on a list.

    Sorry if I'm missing something here :) Just trying to figure out the best way of doing this...and maybe I'm a bit slow :)

    /Jan

  • Jan Skovgaard 11280 posts 23678 karma points MVP 11x admin c-trib
    Mar 08, 2015 @ 19:43
    Jan Skovgaard
    0

    Hi guys

    I just found this https://gist.github.com/tugberkugurlu/2870461 - Would I be able to use this for my described scenario? I'm not sure how to put it into practice though but perhaps it makes sense to you guys? I'm not sure what should be placed in the part where the comment reads "Insert the place locations here...".

    Another options that seems to fit my scenario is this example http://stackoverflow.com/questions/12835851/find-closest-location-with-longitude-and-latitude#answer-12860259 - Again not quite sure how to put it into practice...hints appreciated.

    Cheers, Jan

  • Comment author was deleted

    Mar 09, 2015 @ 11:19

    Hi Jan,

    You'll need to store your data in the geography datatype so not as decimal, once that is done you should be able to query the data pretty easily

  • Comment author was deleted

    Mar 09, 2015 @ 11:21

    Looks like that gist is using entity framework, maybe you should search for a peta poco alternative since that is used internally by Umbraco, if I got some time this week I'll try to write you some example code

  • Comment author was deleted

    Mar 09, 2015 @ 11:34

    Just did a quick test, what you will need to do is move away from the decimal datatype and use geography (you should be able to write a script to do the move, something like https://science.nature.nps.gov/im/units/arcn/data_management/code/210.htm) ;

    Then create a stored procedure that takes in the location you want to search from, query will look like https://msdn.microsoft.com/en-us/library/ff929109.aspx

    And then use peta poco to execute that stored procedure and fetch the results http://stackoverflow.com/questions/20126844/execute-stored-procedure-with-petapoco

    Any change you can share a create script for your table that holds the data?

  • Jan Skovgaard 11280 posts 23678 karma points MVP 11x admin c-trib
    Mar 09, 2015 @ 18:05
    Jan Skovgaard
    0

    Hi Tim

    Awesome, thanks - Will have a look later this evening. I'll see if I can script it and send you a link using Twitter.

    Cheers, Jan

  • Jan Skovgaard 11280 posts 23678 karma points MVP 11x admin c-trib
    Mar 09, 2015 @ 21:06
    Jan Skovgaard
    0

    Hi Tim

    I have completed step one of creating the coordinates column based on the approach mentioned in the first link you provided.

    Below is a sample of my hotels database - Is it enough for you to do some tests?

    CREATE TABLE [dbo].[Hotels](
        [Id] [int] NOT NULL,
        [EANHotelID] [int] NOT NULL,
        [Name] [nvarchar](200) NULL,
        [Address1] [nvarchar](200) NULL,
        [City] [nvarchar](150) NULL,
        [ZipCode] [nvarchar](50) NULL,
        [CountryCode] [nvarchar](10) NULL,
        [Country] [nvarchar](100) NULL,
        [Latitude] [float] NULL,
        [Longitude] [float] NULL,
        [StarRating] [nvarchar](10) NULL,
        [Confidence] [nvarchar](10) NULL,
        [Location] [nvarchar](max) NULL,
        [Coordinates] [geography] NULL
    ) ON [PRIMARY]
    GO
    INSERT [dbo].[Hotels] ([Id], [EANHotelID], [Name], [Address1], [City], [ZipCode], [CountryCode], [Country], [Latitude], [Longitude], [StarRating], [Confidence], [Location], [Coordinates]) VALUES (1, 153416, N'Grange Holborn', N'50-60 Southampton Row', N'London', N'WC1B4AR', N'GB', NULL, 51.51911, -0.12108, N'5.0', N'35', N'Near Covent Garden Market', 0xE6100000010C43AD69DE71C249403A1E335019FFBEBF)
    INSERT [dbo].[Hotels] ([Id], [EANHotelID], [Name], [Address1], [City], [ZipCode], [CountryCode], [Country], [Latitude], [Longitude], [StarRating], [Confidence], [Location], [Coordinates]) VALUES (2, 234065, N'Hotel Panorama', N'Via Santa Tecla 8', N'Maiori', N'84010', N'IT', NULL, 40.64899, 14.63916, N'4.0', N'52', N'Near Roman Villa', 0xE6100000010C508D976E12534440D734EF3845472D40)
    INSERT [dbo].[Hotels] ([Id], [EANHotelID], [Name], [Address1], [City], [ZipCode], [CountryCode], [Country], [Latitude], [Longitude], [StarRating], [Confidence], [Location], [Coordinates]) VALUES (3, 274481, N'Pervanovo Apartments', N'Vatroslava Lisinskog 43 - 59', N'Dubrovnik', N'20000', N'HR', NULL, 42.66194, 18.07034, N'4.0', N'52', N'In Dubrovnik (Lapad)', 0xE6100000010C789CA223B9544540A7E8482EFF113240)
    INSERT [dbo].[Hotels] ([Id], [EANHotelID], [Name], [Address1], [City], [ZipCode], [CountryCode], [Country], [Latitude], [Longitude], [StarRating], [Confidence], [Location], [Coordinates]) VALUES (4, 424013, N'Holiday Inn Resort Krabi Ao Nang Beach', N'123 Moo 3, Ao Nang Beach,', N'Krabi', N'81000', N'TH', NULL, 8.04509, 98.80824, N'4.0', N'52', N'Near Nopparat Thara Beach', 0xE6100000010CD7C056091617204024287E8CB9B35840)
    INSERT [dbo].[Hotels] ([Id], [EANHotelID], [Name], [Address1], [City], [ZipCode], [CountryCode], [Country], [Latitude], [Longitude], [StarRating], [Confidence], [Location], [Coordinates]) VALUES (5, 262515, N'Courtyard by Marriott Phoenix North/Happy Valley', N'2029 W Whispering Wind Dr', N'Phoenix', N'85085', N'US', NULL, 33.71102, -112.10286, N'3.0', N'95', N'In Phoenix (Northern Phoenix)', 0xE6100000010C5EBA490C02DB40403BDF4F8D97065CC0)
    INSERT [dbo].[Hotels] ([Id], [EANHotelID], [Name], [Address1], [City], [ZipCode], [CountryCode], [Country], [Latitude], [Longitude], [StarRating], [Confidence], [Location], [Coordinates]) VALUES (6, 209610, N'Aspen Square Condominium Hotel', N'617 E Cooper Ave', N'Aspen', N'81611', N'US', NULL, 39.18783, -106.81742, N'3.5', N'52', N'Near Aspen Mountain', 0xE6100000010C2B1895D4099843403F355EBA49B45AC0)
    INSERT [dbo].[Hotels] ([Id], [EANHotelID], [Name], [Address1], [City], [ZipCode], [CountryCode], [Country], [Latitude], [Longitude], [StarRating], [Confidence], [Location], [Coordinates]) VALUES (7, 247786, N'Lemon Tree Amarante Beach Resort, Goa', N'Vadi Candolim Bardez Goa', N'Candolim', N'403515', N'IN', NULL, 15.51454, 73.76845, N'4.0', N'52', N'Near Candolim Beach', 0xE6100000010C8195438B6C072F4077BE9F1A2F715240)
    INSERT [dbo].[Hotels] ([Id], [EANHotelID], [Name], [Address1], [City], [ZipCode], [CountryCode], [Country], [Latitude], [Longitude], [StarRating], [Confidence], [Location], [Coordinates]) VALUES (8, 323321, N'Ambre - All Inclusive', N'Coastal Road', N'Belle Mare', NULL, N'MU', NULL, -20.20598, 57.78621, N'4.0', N'52', N'In Belle Mare (Palmar)', 0xE6100000010CDBF97E6ABC3434C0BF0E9C33A2E44C40)
    INSERT [dbo].[Hotels] ([Id], [EANHotelID], [Name], [Address1], [City], [ZipCode], [CountryCode], [Country], [Latitude], [Longitude], [StarRating], [Confidence], [Location], [Coordinates]) VALUES (9, 135723, N'Ports of Call Resort', N'Grace Bay Road', N'Providenciales', NULL, N'TC', NULL, 21.79532, -72.17854, N'3.0', N'5', N'In Providenciales (Grace Bay)', 0xE6100000010C4182E2C798CB35408195438B6C0B52C0)
    INSERT [dbo].[Hotels] ([Id], [EANHotelID], [Name], [Address1], [City], [ZipCode], [CountryCode], [Country], [Latitude], [Longitude], [StarRating], [Confidence], [Location], [Coordinates]) VALUES (10, 358033, N'Aquamarine Resort & Villa', N'17/38 Moo 6, Kamala Beach', N'Kamala', N'83150', N'TH', NULL, 7.94647, 98.26784, N'4.0', N'52', N'Near Phuket Fantasea', 0xE6100000010C9A94826E2FC91F409A779CA223915840)
    INSERT [dbo].[Hotels] ([Id], [EANHotelID], [Name], [Address1], [City], [ZipCode], [CountryCode], [Country], [Latitude], [Longitude], [StarRating], [Confidence], [Location], [Coordinates]) VALUES (11, 228463, N'Kempinski Hotel Grand Arena', N'Pirin Street 96', N'Bansko', N'2770', N'BG', NULL, 41.82624, 23.47842, N'5.0', N'52', N'Near Bansko Ski Resort', 0xE6100000010C44FAEDEBC0E944400A68226C787A3740)
    INSERT [dbo].[Hotels] ([Id], [EANHotelID], [Name], [Address1], [City], [ZipCode], [CountryCode], [Country], [Latitude], [Longitude], [StarRating], [Confidence], [Location], [Coordinates]) VALUES (12, 208300, N'Vail Marriott Resort And Spa', N'715 West Lionshead Circle', N'Vail', N'81657', N'US', NULL, 39.64333, -106.39102, N'4.0', N'91', N'Near Vail Ski Resort', 0xE6100000010C27C286A757D243401B2FDD2406995AC0)
    INSERT [dbo].[Hotels] ([Id], [EANHotelID], [Name], [Address1], [City], [ZipCode], [CountryCode], [Country], [Latitude], [Longitude], [StarRating], [Confidence], [Location], [Coordinates]) VALUES (13, 239040, N'The Boathouse Hotel', N'Kamari Beach Rd', N'Santorini', N'84700', N'GR', NULL, 36.37969, 25.48684, N'3.0', N'52', N'Near Kamari Beach', 0xE6100000010C462575029A3042405BB1BFEC9E7C3940)
    INSERT [dbo].[Hotels] ([Id], [EANHotelID], [Name], [Address1], [City], [ZipCode], [CountryCode], [Country], [Latitude], [Longitude], [StarRating], [Confidence], [Location], [Coordinates]) VALUES (14, 150309, N'Hampton Inn Pennsville', N'429 N Broadway', N'Pennsville', N'08070', N'US', NULL, 39.6781, -75.4941, N'2.5', N'95', N'In Pennsville', 0xE6100000010C74B515FBCBD64340083D9B559FDF52C0)
    INSERT [dbo].[Hotels] ([Id], [EANHotelID], [Name], [Address1], [City], [ZipCode], [CountryCode], [Country], [Latitude], [Longitude], [StarRating], [Confidence], [Location], [Coordinates]) VALUES (15, 256266, N'Metropolitan Marunouchi Hotel', N'1-7-12 Marunouchi', N'Tokyo', N'100-0005', N'JP', NULL, 35.68395, 139.76919, N'4.0', N'55', N'Near Mitsui Memorial Museum', 0xE6100000010C643BDF4F8DD74140F853E3A59B786140)
    INSERT [dbo].[Hotels] ([Id], [EANHotelID], [Name], [Address1], [City], [ZipCode], [CountryCode], [Country], [Latitude], [Longitude], [StarRating], [Confidence], [Location], [Coordinates]) VALUES (16, 148852, N'The Kensington Court', N'610 Hilton Blvd', N'Ann Arbor', N'48108', N'US', NULL, 42.23949, -83.74128, N'3.0', N'52', N'Near Cobblestone Farm', 0xE6100000010C2DB29DEFA71E454096218E7571EF54C0)
    INSERT [dbo].[Hotels] ([Id], [EANHotelID], [Name], [Address1], [City], [ZipCode], [CountryCode], [Country], [Latitude], [Longitude], [StarRating], [Confidence], [Location], [Coordinates]) VALUES (17, 182130, N'Divi Aruba All Inclusive', N'J E Irausquin Boulevard 45', N'Oranjestad', NULL, N'AW', NULL, 12.53812, -70.06047, N'3.5', N'52', N'Near Druif Beach', 0xE6100000010C88F4DBD7811329401D5A643BDF8351C0)
    INSERT [dbo].[Hotels] ([Id], [EANHotelID], [Name], [Address1], [City], [ZipCode], [CountryCode], [Country], [Latitude], [Longitude], [StarRating], [Confidence], [Location], [Coordinates]) VALUES (18, 462444, N'SeaWatch Plantation', N'151 SeaWatch Drive', N'Myrtle Beach', N'29572', N'US', NULL, 33.77218, -78.77198, N'3.5', NULL, N'Near Arcadian Shores Golf Club', 0xE6100000010C50FC1873D7E24040C520B07268B153C0)
    INSERT [dbo].[Hotels] ([Id], [EANHotelID], [Name], [Address1], [City], [ZipCode], [CountryCode], [Country], [Latitude], [Longitude], [StarRating], [Confidence], [Location], [Coordinates]) VALUES (19, 143399, N'Be Live Family Lanzarote Resort', N'Av. del Mar, s/n', N'Teguise', N'35509', N'ES', NULL, 28.98654, -13.5106, N'4.0', N'52', N'In Teguise (Costa Teguise)', 0xE6100000010C068195438BFC3C40DAACFA5C6D052BC0)
    INSERT [dbo].[Hotels] ([Id], [EANHotelID], [Name], [Address1], [City], [ZipCode], [CountryCode], [Country], [Latitude], [Longitude], [StarRating], [Confidence], [Location], [Coordinates]) VALUES (20, 370309, N'Galaxy Hotel', N'ul. Gesia 22a', N'Krakow', N'31-535', N'PL', NULL, 50.05342, 19.95792, N'4.0', N'52', N'Near Galicia Jewish Museum', 0xE6100000010C9FCDAACFD5064940A1D634EF38F53340)
    INSERT [dbo].[Hotels] ([Id], [EANHotelID], [Name], [Address1], [City], [ZipCode], [CountryCode], [Country], [Latitude], [Longitude], [StarRating], [Confidence], [Location], [Coordinates]) VALUES (21, 218097, N'L''Hotel du Collectionneur Arc de Triomphe', N'51-57 rue de Courcelles', N'Paris', N'75008', N'FR', NULL, 48.87707, 2.30705, N'5.0', N'48', N'Near Arc de Triomphe', 0xE6100000010C2AA913D044704840F7E461A1D6740240)
    INSERT [dbo].[Hotels] ([Id], [EANHotelID], [Name], [Address1], [City], [ZipCode], [CountryCode], [Country], [Latitude], [Longitude], [StarRating], [Confidence], [Location], [Coordinates]) VALUES (22, 122414, N'Four Seasons Resort Rancho Encantado Santa Fe', N'198 State Road 592', N'Santa Fe', N'87506', N'US', NULL, 35.77876, -105.94147, N'5.0', N'52', N'Near Santa Fe Opera', 0xE6100000010CF90FE9B7AFE341404E621058397C5AC0)
    INSERT [dbo].[Hotels] ([Id], [EANHotelID], [Name], [Address1], [City], [ZipCode], [CountryCode], [Country], [Latitude], [Longitude], [StarRating], [Confidence], [Location], [Coordinates]) VALUES (23, 262612, N'The Westin Princeville Ocean Resort Villas', N'3838 Wyllie Rd', N'Princeville', N'96722', N'US', NULL, 22.22189, -159.46852, N'4.0', N'52', N'In Princeville (Princeville - Hanalei)', 0xE6100000010C7FFB3A70CE3836405EBA490C02EF63C0)
    INSERT [dbo].[Hotels] ([Id], [EANHotelID], [Name], [Address1], [City], [ZipCode], [CountryCode], [Country], [Latitude], [Longitude], [StarRating], [Confidence], [Location], [Coordinates]) VALUES (24, 219106, N'Cypress Inn On Miramar Beach', N'407 Mirada Rd', N'Half Moon Bay', N'94019', N'US', NULL, 37.49377, -122.46045, N'3.0', N'52', N'In Half Moon Bay (El Granada)', 0xE6100000010CE561A1D634BF42403D0AD7A3709D5EC0)
    INSERT [dbo].[Hotels] ([Id], [EANHotelID], [Name], [Address1], [City], [ZipCode], [CountryCode], [Country], [Latitude], [Longitude], [StarRating], [Confidence], [Location], [Coordinates]) VALUES (25, 114105, N'Eden au Lac', N'Utoquai 45', N'Zurich', N'8008', N'CH', NULL, 47.36273, 8.54717, N'5.0', N'78', N'Near Fraumuenster', 0xE6100000010C2D211FF46CAE474063B48EAA26182140)
    INSERT [dbo].[Hotels] ([Id], [EANHotelID], [Name], [Address1], [City], [ZipCode], [CountryCode], [Country], [Latitude], [Longitude], [StarRating], [Confidence], [Location], [Coordinates]) VALUES (26, 221218, N'Palace Bonvecchiati', N'San Marco', N'Venice', N'30124', N'IT', NULL, 45.43544, 12.33662, N'4.0', N'88', N'Near St. Mark''s Basilica', 0xE6100000010CD656EC2FBBB74640CEAACFD556AC2840)
    INSERT [dbo].[Hotels] ([Id], [EANHotelID], [Name], [Address1], [City], [ZipCode], [CountryCode], [Country], [Latitude], [Longitude], [StarRating], [Confidence], [Location], [Coordinates]) VALUES (27, 218758, N'Hotel Straf', N'Via San Raffaele 3', N'Milan', N'20121', N'IT', NULL, 45.46501, 9.1914, N'4.0', N'95', N'Near Cathedral of Milan', 0xE6100000010CEC51B81E85BB4640A7E8482EFF612240)
    INSERT [dbo].[Hotels] ([Id], [EANHotelID], [Name], [Address1], [City], [ZipCode], [CountryCode], [Country], [Latitude], [Longitude], [StarRating], [Confidence], [Location], [Coordinates]) VALUES (28, 238919, N'Intertur Hotel Hawaii Mallorca & Suites', N'Calle Torrenova, 1', N'Calvia', N'07181', N'ES', NULL, 39.51218, 2.54163, N'4.0', N'52', N'In Calvia (Magaluf)', 0xE6100000010C6F8104C58FC143408542041C42550440)
    INSERT [dbo].[Hotels] ([Id], [EANHotelID], [Name], [Address1], [City], [ZipCode], [CountryCode], [Country], [Latitude], [Longitude], [StarRating], [Confidence], [Location], [Coordinates]) VALUES (29, 389989, N'Al Ghurair Rayhaan By Rotana', N'Deira', N'Dubai', NULL, N'AE', NULL, 25.26944, 55.31644, N'5.0', N'52', N'In Dubai (Deira)', 0xE6100000010CDB8AFD65F74439402A3A92CB7FA84B40)
    INSERT [dbo].[Hotels] ([Id], [EANHotelID], [Name], [Address1], [City], [ZipCode], [CountryCode], [Country], [Latitude], [Longitude], [StarRating], [Confidence], [Location], [Coordinates]) VALUES (30, 236980, N'Sol House Guadalupe', N'Carretera La Porrasa, S/n', N'Calvia', N'07181', N'ES', NULL, 39.50545, 2.5303, N'4.0', N'52', N'Near Pirates Adventure Show', 0xE6100000010C62105839B4C04340E78C28ED0D3E0440)
    INSERT [dbo].[Hotels] ([Id], [EANHotelID], [Name], [Address1], [City], [ZipCode], [CountryCode], [Country], [Latitude], [Longitude], [StarRating], [Confidence], [Location], [Coordinates]) VALUES (31, 370313, N'Westmont Inn Clearwater Beach, FL', N'355 S Gulfview Blvd', N'Clearwater Beach', N'33767', N'US', NULL, 27.97132, -82.8294, N'2.0', N'96', N'Near Pier 60 Park', 0xE6100000010CD49AE61DA7F83B405BD3BCE314B554C0)
    INSERT [dbo].[Hotels] ([Id], [EANHotelID], [Name], [Address1], [City], [ZipCode], [CountryCode], [Country], [Latitude], [Longitude], [StarRating], [Confidence], [Location], [Coordinates]) VALUES (32, 408634, N'Helzear Montparnasse Rive Gauche Apartments', N'40 Rue Liancourt', N'Paris', N'75014', N'FR', NULL, 48.83466, 2.32506, N'4.0', N'100', N'Near Cartier Foundation for Contemporary Art', 0xE6100000010C50FC1873D76A4840EFE6A90EB9990240)
    INSERT [dbo].[Hotels] ([Id], [EANHotelID], [Name], [Address1], [City], [ZipCode], [CountryCode], [Country], [Latitude], [Longitude], [StarRating], [Confidence], [Location], [Coordinates]) VALUES (33, 368370, N'Inturotel Esmeralda Garden', N'Avenida de Sa Punta Grossa, s/n', N'Santanyi', N'07660', N'ES', NULL, 39.37725, 3.23352, N'3.0', N'52', N'In Santanyi (Cala d''Or)', 0xE6100000010C8E06F01648B0434003B2D7BB3FDE0940)
    

    Cheers, Jan

  • Jan Skovgaard 11280 posts 23678 karma points MVP 11x admin c-trib
    Mar 09, 2015 @ 21:26
    Jan Skovgaard
    0

    Hmm, seems like I'm not able to use a spatial index on MSSQL 2008 express? I'm getting this when I try running the code from you 2nd example "Cannot find either column "SpatialLocation" or the user-defined function or aggregate "SpatialLocation.ToString", or the name is ambiguous."

    /Jan

  • Comment author was deleted

    Mar 10, 2015 @ 13:32

    @Jan spatiallocation is the column containing the geography data :) so that will change depending on how you named that column

  • Comment author was deleted

    Mar 10, 2015 @ 13:32

    So in your case it should be Coordinates

  • Comment author was deleted

    Mar 10, 2015 @ 13:36

    So query will look like 

    DECLARE @g geography='POINT(-121.626 47.8315)';

    SELECT TOP(5) Coordinates.ToString(), * FROM Hotels

     

    ORDER BY Coordinates.STDistance(@g);

  • Jan Skovgaard 11280 posts 23678 karma points MVP 11x admin c-trib
    Mar 10, 2015 @ 14:59
    Jan Skovgaard
    0

    Hi Tim

    Ah yes, of course! That works fine...however the query is taking 24 seconds...But I guess the performance can be fine tuned? And do you know if it would be possible to have the distance calculated in kilometers for instance?

    Cheers, Jan

  • Comment author was deleted

    Mar 11, 2015 @ 11:09

    Not a sql guy but I assume it can be made more performant , how many entries do you have in your database?

  • Jan Skovgaard 11280 posts 23678 karma points MVP 11x admin c-trib
    Mar 11, 2015 @ 18:12
    Jan Skovgaard
    0

    Hi Tim

    Around 120.000 :)

    /Jan

  • Comment author was deleted

    Mar 11, 2015 @ 18:13

    Oh my :) well you need to find yourself a db wizard to see if things can be speed up 

  • Comment author was deleted

    Mar 11, 2015 @ 18:21

    Seems this offers some help http://blogs.msdn.com/b/isaac/archive/2008/10/23/nearest-neighbors.aspx several alternative queries you can give a try

  • Comment author was deleted

    Mar 11, 2015 @ 18:40

    Can you try the following

    First do:

    SELECTTOP 100000 IDENTITY(int,1,1)AS

    INTO numbers 

    FROM MASTER..spt_values a, MASTER..spt_values b

     CREATEUNIQUECLUSTEREDINDEX idx_1 ON numbers(n)

    Then do 

    DECLARE @g geography='POINT(-121.626 47.8315)';

     

    DECLARE @start FLOAT = 1000; 

     

    WITH NearestPoints AS

    (

       SELECT TOP(1) WITH TIES *,  Hotels.Coordinates.STDistance(@g) AS dist

       FROM Numbers JOIN Hotels 

       ON Hotels.Coordinates.STDistance(@g) < @start*POWER(2,Numbers.n)

       ORDERBY n

    )

    SELECT TOP(1) * FROM NearestPoints

     

    ORDER BY n, dist

  • Jan Skovgaard 11280 posts 23678 karma points MVP 11x admin c-trib
    Mar 11, 2015 @ 20:11
    Jan Skovgaard
    0

    Hi Tim

    I just tried but unfortunately it does not make any difference. The query takes around 24-26 seconds to execute still :-/

    Appreciate the effort though :)

    /Jan

  • Comment author was deleted

    Mar 11, 2015 @ 20:33

    dammit :( wel not sure what you can do next...

  • Jan Skovgaard 11280 posts 23678 karma points MVP 11x admin c-trib
    Mar 14, 2015 @ 16:34
    Jan Skovgaard
    0

    Hi Tim

    I'm not sure how it works exactly but I found this on another forum and it's executing really fast and seems to be reliable.

    DECLARE @Location GEOGRAPHY
    SET @Location = GEOGRAPHY::STPointFromText('POINT(-71.0571571 42.3133735)',4326).STBuffer(10000);
    SELECT TOP(10) Coordinates.ToString(), * FROM Hotels WHERE Coordinates.STIntersects(@Location) = 1
    ORDER BY Coordinates.STDistance(@Location);
    

    Now I'm just not sure what the next step would be in order to be able to expose it in a view? And how would I get the distance displayed in Kilometers?

    /Jan

  • Jan Skovgaard 11280 posts 23678 karma points MVP 11x admin c-trib
    Mar 14, 2015 @ 22:00
    Jan Skovgaard
    0

    Ok, so I think I have figured out how to fetch the data and render it in my view...however I'm currently stuck with my POC since I get this error

    Object must implement IConvertible. Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code. Exception Details: System.InvalidCastException: Object must implement IConvertible. Source Error: Line 19: public IEnumerable

    I have a model that looks like this

    using System;
    using System.Data.Entity.Spatial;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using Umbraco.Core.Persistence;
    
    namespace Hotels.Models
    {
        [TableName("Hotels")]
        [PrimaryKey("Id", autoIncrement = true)]
        public class Hotel
        {
            public int Id { get; set; }
            public int EANHotelID { get; set; }
            public string Name { get; set; }
            public string Address1 { get; set; }
            public string City { get; set; }
            public string CountryCode { get; set; }
            public string Country { get; set; }
            public float Latitude { get; set; }
            public float Longitude { get; set; }
            public string StarRating { get; set; }
            public string Confidence { get; set; }
            public string Location { get; set; }
            public DbGeography Coordinates { get; set; }
            public string ZipCode { get; set; }
        }
    }
    

    My Controller looks like this

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using Hotels.Models;
    using Umbraco.Core;
    using Umbraco.Core.Persistence;
    
    namespace Hotels.Controllers
    {
        public class HotelRepository
        {
            private readonly UmbracoDatabase _database;
    
            public HotelRepository() {
                _database = ApplicationContext.Current.DatabaseContext.Database;
            }
    
            public IEnumerable<Hotel> GetAll()
            {
                return _database.Fetch<Hotel>("SELECT TOP 2 * FROM Hotels");
            }
    
        }
    }
    

    And finally my view looks like this

    @using Hotels.Controllers
    @using Hotels.Models
    @inherits Umbraco.Web.Mvc.UmbracoTemplatePage
    
    @{
        Layout = "Master.cshtml";
        var repo = new HotelRepository();
    }
    
    @{
    
        foreach (var hotel in repo.GetAll()){
            <p>
                @hotel.City
                <br />
                @hotel.Name
            </p>
        }
    }
    

    Any hints appreciated :)

    /Jan

  • Anders Bjerner 487 posts 2989 karma points MVP 8x admin c-trib
    Mar 15, 2015 @ 14:26
    Anders Bjerner
    1

    Hi Jan,

    I haven't worked with DbGeography and the coordinate type in MSSQL before, so I can't say why it fails. So here is what I would do instead:

    Since you also have Latitude and Longitude in your table and your model, I would simply remove the Coordinates property from your model (but still leave it in the database for searches). After this change, your view will succeed, and fetch the first two hotels.

    Then to get the distance in meters, you can use my Location class:

    using System;
    
    public class Location {
    
        // Based on:
        //   https://github.com/abjerner/Skybrud.Social/blob/master/src/Skybrud.Social/Location.cs
        //   https://github.com/abjerner/Skybrud.Social/blob/master/src/Skybrud.Social/SocialUtils.cs
    
        #region Properties
    
        /// <summary>
        /// Gets the latitude of the location. The latitude specifies the north-south position of a
        /// point on the Earth's surface.
        /// </summary>
        public double Latitude { get; set; }
    
        /// <summary>
        /// Gets the longitude of the location. The longitude specifies the east-west position of a
        /// point on the Earth's surface.
        /// </summary>
        public double Longitude { get; set; }
    
        #endregion
    
        #region Constructors
    
        public Location() {
            // Default constructor
        }
    
        public Location(double latitude, double longitude) {
            Latitude = latitude;
            Longitude = longitude;
        }
    
        #endregion
    
        #region Static methods
    
        /// <summary>
        /// Calculates the distance in meters between two GPS locations.
        /// </summary>
        /// <param name="loc1">The first location.</param>
        /// <param name="loc2">The second location.</param>
        public static double GetDistance(Location loc1, Location loc2) {
            return GetDistance(loc1.Latitude, loc1.Longitude, loc2.Latitude, loc2.Longitude);
        }
    
        /// <summary>
        /// Calculates the distance in meters between two GPS locations.
        /// </summary>
        public static double GetDistance(double lat1, double lng1, double lat2, double lng2) {
    
            // http://stackoverflow.com/a/3440123
            double ee = (Math.PI * lat1 / 180);
            double f = (Math.PI * lng1 / 180);
            double g = (Math.PI * lat2 / 180);
            double h = (Math.PI * lng2 / 180);
            double i = (Math.Cos(ee) * Math.Cos(g) * Math.Cos(f) * Math.Cos(h) + Math.Cos(ee) * Math.Sin(f) * Math.Cos(g) * Math.Sin(h) + Math.Sin(ee) * Math.Sin(g));
            double j = (Math.Acos(i));
    
            return (6371 * j) * 1000d;
    
        }
    
        #endregion
    
    }
    

    Your view could then look like:

    @{
    
        // Get a reference to the database
        UmbracoDatabase db = ApplicationContext.Current.DatabaseContext.Database;
    
        // Set the location we should use for comparison
        Location location = new Location(51.508054, -0.128081);
    
        // Database stuff
        List<Hotel> hotels = db.Fetch<Hotel>("SELECT TOP 2 * FROM Hotels");
    
        foreach (var hotel in hotels) {
    
            Location hotelLocation = new Location(hotel.Latitude, hotel.Longitude);
    
            <p>
                @hotel.City
                <br />
                @hotel.Name
                <br />
                @hotel.Latitude
                <br />
                @hotel.Longitude
                <br />
                @String.Format("{0:N2}", Location.GetDistance(location, hotelLocation) / 1000.00) km
            </p>
    
        }
    
    }
    

    I've skipped your HotelRepistory class in the example above, but that was simply because it was easier for me to test.

  • Jan Skovgaard 11280 posts 23678 karma points MVP 11x admin c-trib
    Mar 15, 2015 @ 15:18
    Jan Skovgaard
    0

    Hi Anders

    Thank you very much for your reply - I'm beginning to learn / get into how petapoco works and how the logic should be defined in MVC serverside wise in an Umbraco context.

    I uncommented the Coordinates line from my code and now it works. But I'm not quite there yet. I need to get the 10 nearest hotels to a known location. I'm thinking I should have this logic in the controller so it's only those 10 hotels that are ever returned to the view. For that I need to execute this SQL code somehow

    DECLARE @Location GEOGRAPHY
    SET @Location = GEOGRAPHY::STPointFromText('POINT(-71.0571571 42.3133735)',4326).STBuffer(10000);
    SELECT TOP(10) Coordinates.ToString(), * FROM Hotels WHERE Coordinates.STIntersects(@Location) = 1
    ORDER BY Coordinates.STDistance(@Location);
    

    It's executing instantly when I fire it in my DB. So I just need to figure out how to get the known location from my CurrentPage passed to a POINT in the query and how it can be constructed in my controller.

    Since I posted my previous question I decided to rewrite my code following Warrens blogpost http://creativewebspecialist.co.uk/2013/07/16/umbraco-petapoco-to-store-blog-comments/ using a surface controller.

    So now my code looks like this

    The model

    using System;
    using System.Data.Entity.Spatial;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Runtime.Serialization;
    
    using Umbraco.Core.Persistence;
    using Umbraco.Core.Persistence.DatabaseAnnotations;
    
    namespace Hotels.Models
    {
        [TableName("Hotels")]
        [DataContract(Name = "hotel")]
        public class Hotel
        {
            [PrimaryKeyColumn(AutoIncrement = true)]
            [DataMember(Name = "id")]
            public int Id { get; set; }
    
            [DataMember(Name = "eanHotelId")]
            public int EANHotelID { get; set; }
    
            [DataMember(Name = "name")]
            public string Name { get; set; }
    
            [DataMember(Name = "address1")]
            public string Address1 { get; set; }
    
            [DataMember(Name = "city")]
            public string City { get; set; }
    
            [DataMember(Name = "countryCode")]
            public string CountryCode { get; set; }
    
            [DataMember(Name = "country")]
            public string Country { get; set; }
    
            [DataMember(Name = "latitude")]
            public float Latitude { get; set; }
    
            [DataMember(Name = "longitude")]
            public float Longitude { get; set; }
    
            [DataMember(Name = "starRating")]
            public string StarRating { get; set; }
    
            [DataMember(Name = "confidence")]
            public string Confidence { get; set; }
    
            [DataMember(Name = "location")]
            public string Location { get; set; }
    
            /*[DataMember(Name = "coordinates")]
            public DbGeography Coordinates { get; set; }*/
    
            [DataMember(Name = "zipCode")]
            public string ZipCode { get; set; }
        }
    }
    

    The surface controller

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;
    using Tourbowler.Models;
    using Umbraco.Core;
    using Umbraco.Web.Mvc;
    
    namespace Hotels.SurfaceControllers
    {
        public class HotelSurfaceController : SurfaceController
        {
    
            public ActionResult RenderHotels() {
    
                var db = ApplicationContext.DatabaseContext.Database;
    
                //Not used here so far
                var currentNodeId = UmbracoContext.PageId.Value;
    
                var hotels = db.Fetch<Hotel>("SELECT TOP 2 * FROM Hotels");
    
                return PartialView("viewHotels", hotels);
    
            }
    
        }
    }
    

    The macro partial view

    @using Hotels.SurfaceControllers
    @using Hotels.Models
    @inherits Umbraco.Web.Mvc.UmbracoViewPage<IEnumerable<Hotel>>
    
        @foreach (var hotel in Model){
            <p>
                @hotel.City
                <br />
                @hotel.Name
            </p>
        }
    

    So I'm thinking I could use the contentService to figure out what known location is stored on the currentpage and then pass the coordinates somehow so that I in the view can just loop over the X returned locations.

    Hope this makes sense :)

    /Jan

  • Anders Bjerner 487 posts 2989 karma points MVP 8x admin c-trib
    Mar 15, 2015 @ 16:06
    Anders Bjerner
    1

    If we just look at making this work, the following snippets does the job. In order to be able to reference the SqlGeography class, you should install the Microsoft.SqlServer.Types package from NuGet. distance is simply an integer with the search distance/radius in meters.

    SqlGeography point = SqlGeography.Point(location.Latitude, location.Longitude, 4326);
    List<Hotel> hotels = db.Fetch<Hotel>(
        "SELECT * FROM Hotels WHERE @0.STDistance(Coordinates) <= " + distance + " ORDER BY @0.STDistance(Coordinates) ASC",
        point.STBuffer(10000)
    );
    

    The snippet may calculate the distance twice for each row since the distance is sued both for the WHERE clause and for ordering the rows. I can't tell whether this affect performance based on the rows you posted earlier.

    To only return the nearest five hotels, you could simply update the SQL to:

    SqlGeography point = SqlGeography.Point(location.Latitude, location.Longitude, 4326);
    List<Hotel> hotels = db.Fetch<Hotel>(
        "SELECT TOP 5 * FROM Hotels WHERE @0.STDistance(Coordinates) <= " + distance + " ORDER BY @0.STDistance(Coordinates) ASC",
        point.STBuffer(10000)
    );
    

    I'm not sure that I understand the purpose of the surface controller. Having a repository like the one you posted earlier is generally a very good idea, since you have isolated the code responsible for the database stuff, and then you simply use your repository in the rest of your code rather than doing the database stuff again. If the surface controller makes sense to you, it could be updated to use the repository.

    If you stick with the repository class, my take would look like:

    using System.Collections.Generic;
    using Microsoft.SqlServer.Types;
    using Umbraco.Core;
    using Umbraco.Core.Persistence;
    
    namespace Hotels.Models {
    
        public class HotelRepository {
    
            private readonly UmbracoDatabase _database;
    
            public HotelRepository() {
                _database = ApplicationContext.Current.DatabaseContext.Database;
            }
    
            public IEnumerable<Hotel> GetAll() {
                return _database.Fetch<Hotel>("SELECT TOP 2 * FROM Hotels");
            }
    
            /// <summary>
            /// Gets a list of all hotels within the specified <code>radius</code> from <code>location</code>.
            /// </summary>
            /// <param name="location">The location used as reference.</param>
            /// <param name="radius">The search radious specified in meters.</param>
            /// <param name="count">The maximum amount of hotels to return. If set to <code>0</code>, all hotels within <code>distance</code> will be returned.</param>
            public List<Hotel> GetAll(Location location, int radius, int count = 0) {
    
                SqlGeography point = SqlGeography.Point(location.Latitude, location.Longitude, 4326);
    
                return _database.Fetch<Hotel>(
                    "SELECT " + (count > 0 ? "TOP " + count + " " : "") + "* FROM Hotels WHERE @0.STDistance(Coordinates) <= " + radius + " ORDER BY @0.STDistance(Coordinates) ASC",
                    point.STBuffer(10000)
                );
    
            }
    
        }
    
    }
    

    Usage would be something like (no limit):

    Location location = new Location(51.508054, -0.128081);
    HotelRepository repo = new HotelRepository();
    List<Hotel> hotels = repo.GetAll(location, distance);
    

    or (with limit):

    Location location = new Location(51.508054, -0.128081);
    HotelRepository repo = new HotelRepository();
    List<Hotel> hotels = repo.GetAll(location, distance, 5);
    

    Also, to simplify your view a bit, you could add a location/coordinates property to your Hotel class rather initializing the location in the view:

    using Umbraco.Core.Persistence;
    
    namespace Hotels.Models {
        [TableName("Hotels")]
        [PrimaryKey("Id", autoIncrement = true)]
        public class Hotel {
    
            private Location _location;
    
            public int Id { get; set; }
            public int EANHotelID { get; set; }
            public string Name { get; set; }
            public string Address1 { get; set; }
            public string City { get; set; }
            public string CountryCode { get; set; }
            public string Country { get; set; }
            public float Latitude { get; set; }
            public float Longitude { get; set; }
            public string StarRating { get; set; }
            public string Confidence { get; set; }
            public string Location { get; set; }
            //public DbGeography Coordinates { get; set; }
            public string ZipCode { get; set; }
    
            [Ignore]
            public Location Coordinates {
                get { return _location ?? (_location = new Location(Latitude, Longitude)); }
            }
    
        }
    }
    
  • Jan Skovgaard 11280 posts 23678 karma points MVP 11x admin c-trib
    Mar 15, 2015 @ 16:18
    Jan Skovgaard
    0

    Hi Anders

    Thanks a lot - The above stuff is awesome! :)

    The only reason I switched to using a surface controller is honestly because I have no clue as to what I'm doing really. I found a video on youtube where the Repository approach was being used but then along the way it was said..."Ok, this is just for coding speed but is not the best way to do it" once the controller was tied to the view.

    So in short I'm happy to keep using the HotelRepository...I just need to figure out what the best approach to tie it to my view is? :)

    And finally...how would I be able to add the distance in KM's to be displayed in my view? I suppose I can use some of the code you posted earlier...but not sure about the context.

    /Jan

  • Jan Skovgaard 11280 posts 23678 karma points MVP 11x admin c-trib
    Mar 15, 2015 @ 16:54
    Jan Skovgaard
    0

    Hmm, getting

    Error 2 'Hotels.Models.Hotel.Location' is a 'property' but is used like a 'type' in my model where I declare this

        [Ignore]
        public Location Coordinates {
            get { return _location ?? (_location = new _location(Latitude, Longitude)); }
        }
    

    and in the controller I get this message - Even though I have installed the Microsoft.SqlServer.Types using nuget and referenced it in my code?

    Error 1 The type or namespace name 'Location' could not be found (are you missing a using directive or an assembly reference?)

    The code is copy/pasted from the example. What to do? :)

    /Jan

  • Anders Bjerner 487 posts 2989 karma points MVP 8x admin c-trib
    Mar 15, 2015 @ 16:58
    Anders Bjerner
    1

    Repository vs surface controller
    The idea of the repository is to isolate the code into pieces that can be re-used, and then only have a single place in the code to maintain the database stuff. The repository doesn't exclude the surface controller though.

    Say you start out by implementing the surface controller, and make the calls to the database directly in the class. Things will work fine.

    Then a month later, you need do something else, that also needs to pull hotels from the database, but yet is so different that you can't use your surface controller from before. If you just copy the bits of the code that you need, and place it in a new surface controller, WebApi controller or wherever you need it, you will end up having two almost identical pieces of code.

    Then a bit later, a change needs to be made to the "Hotels" table in the database, and you will now have to update your code two places in your code.

    If you had initially implemented the repository, your would most likely only have had to update the repository class, and the rest of your code would work fine again.

    The repository can somewhat be compared to the service layer in Umbraco. Everything in Umbraco uses the services - even the backoffice. The Umbraco services are probably a bit more complex, but the idea is the same: the places in your code you need to update/maintain is minimized.

    Your view
    Anyways, given the changes in my previous post, my view would look like:

    @{
    
        // Set the location we should use for comparison (Trafalgar Square)
        Location location = new Location(51.508054, -0.128081);
    
        // Initialize a new instance of the repository
        HotelRepository repo = new HotelRepository();
    
        // Declare the distance (here 5000 meters)
        int distance = 5000;
    
        // Get a list of the 10 nearest hotels
        List<Hotel> hotels = repo.GetAll(location, distance, 10);
    
        // List each hotel
        <table border="1">
            @foreach (var hotel in hotels) {
                <tr>
                    <td>@hotel.City</td>
                    <td>@hotel.Name</td>
                    <td>@hotel.Latitude</td>
                    <td>@hotel.Longitude</td>
                    <td>@Location.GetDistance(location, hotel.Coordinates) meters</td>
                    <td>@String.Format("{0:N2}", Location.GetDistance(location, hotel.Coordinates)/1000.00) km</td>
                </tr>
            }
        </table>
    
    }
    
  • Anders Bjerner 487 posts 2989 karma points MVP 8x admin c-trib
    Mar 15, 2015 @ 17:00
    Anders Bjerner
    1

    Regarding your build errors, Location is my custom class posted in this comment: https://our.umbraco.org/forum/developers/razor/61832-Find-top-10-nearest-locations-from-DB-near-known-location?p=2#comment212701

    Adding this to the project should fix the build errors.

  • Jan Skovgaard 11280 posts 23678 karma points MVP 11x admin c-trib
    Mar 15, 2015 @ 17:04
    Jan Skovgaard
    0

    Hi Anders

    Ah yes, of course :) Added the class now but now getting

    Error 3 The name '_location' does not exist in the current context in the model?

    /Jan

  • Anders Bjerner 487 posts 2989 karma points MVP 8x admin c-trib
    Mar 15, 2015 @ 17:12
    Anders Bjerner
    1

    Seems like you need the private field - see line 8:

    using Umbraco.Core.Persistence;
    
    namespace Hotels.Models {
        [TableName("Hotels")]
        [PrimaryKey("Id", autoIncrement = true)]
        public class Hotel {
    
            private Location _location;
    
            public int Id { get; set; }
            public int EANHotelID { get; set; }
            public string Name { get; set; }
            public string Address1 { get; set; }
            public string City { get; set; }
            public string CountryCode { get; set; }
            public string Country { get; set; }
            public float Latitude { get; set; }
            public float Longitude { get; set; }
            public string StarRating { get; set; }
            public string Confidence { get; set; }
            public string Location { get; set; }
            //public DbGeography Coordinates { get; set; }
            public string ZipCode { get; set; }
    
            [Ignore]
            public Location Coordinates {
                get { return _location ?? (_location = new Location(Latitude, Longitude)); }
            }
    
        }
    }
    
  • Jan Skovgaard 11280 posts 23678 karma points MVP 11x admin c-trib
    Mar 15, 2015 @ 17:20
    Jan Skovgaard
    0

    Hi Anders

    Thanks for your patience - Overlooked that one. Build finally succeeded. But now I'm getting this error in the log file when i'm trying to render my partial view

    2015-03-15 17:15:33,497 [39] WARN umbraco.macro - [Thread 31] Error loading Partial View (file: ~/Views/MacroPartials/GetNearByHotels.cshtml). Exception: System.InvalidOperationException: The model item passed into the dictionary is of type 'Umbraco.Web.Models.PartialViewMacroModel', but this dictionary requires a model item of type 'System.Collections.Generic.IEnumerable`1[Hotels.Models.Hotel]'.

    My view looks like this

    @using Hotels.Controllers
    @using Hotels.Models
    @inherits Umbraco.Web.Mvc.UmbracoViewPage<IEnumerable<Hotel>>
    
    @{
    
        // Set the location we should use for comparison (Trafalgar Square)
        Location location = new Location(51.508054, -0.128081);
    
        // Initialize a new instance of the repository
        HotelRepository repo = new HotelRepository();
    
        // Declare the distance (here 5000 meters)
        int distance = 5000;
    
        // Get a list of the 10 nearest hotels
        List<Hotel> hotels = repo.GetAll(location, distance, 10);
    
        // List each hotel
        <table border="1">
            @foreach (var hotel in hotels) {
                <tr>
                    <td>@hotel.City</td>
                    <td>@hotel.Name</td>
                    <td>@hotel.Latitude</td>
                    <td>@hotel.Longitude</td>
                    <td>@Location.GetDistance(location, hotel.Coordinates) meters</td>
                    <td>@String.Format("{0:N2}", Location.GetDistance(location, hotel.Coordinates)/1000.00) km</td>
                </tr>
            }
        </table>
    
    }
    

    So what am I doing wrong here? :)

    /Jan

  • Anders Bjerner 487 posts 2989 karma points MVP 8x admin c-trib
    Mar 15, 2015 @ 17:36
    Anders Bjerner
    100

    As your view is right now,

    @inherits Umbraco.Web.Mvc.UmbracoViewPage<IEnumerable<Hotel>>
    

    tells the compiler that the type of the expected model (could be described as a kind of scope) is IEnumerable<Hotel>, but is instead of the type Umbraco.Web.Models.PartialViewMacroModel. To fix it, simply replace the line with:

    @inherits Umbraco.Web.Macros.PartialViewMacroPage
    
  • Jan Skovgaard 11280 posts 23678 karma points MVP 11x admin c-trib
    Mar 15, 2015 @ 17:56
    Jan Skovgaard
    0

    Anders, you're da man! :) Thank you very very much for all you help - It's freaking working -Yipee! #h5yr sir!

    I had to switch the coordinates though...for some reason when using POINT it's expecting Lng/Lat...instead og Lat/Lng. But once I switched those it worked like a charm.

    /Jan

  • Anders Bjerner 487 posts 2989 karma points MVP 8x admin c-trib
    Mar 15, 2015 @ 17:59
    Anders Bjerner
    0

    Glad to see it work.

    As far as I could tell it still worked fine even though the coordinates are stored as lng/lat in the database?

  • Jan Skovgaard 11280 posts 23678 karma points MVP 11x admin c-trib
    Mar 15, 2015 @ 18:11
    Jan Skovgaard
    0

    I have not taken the time to get fully into what the code does - But in regards to the query then I don't think it would succeed without making use of the geography datatype (Coordinates) since there is a spatial index on this column...if that's what you're thinking about?

    However I can see there is some weird stuff going on with calculations...I just tested with a coordinate for my own address - I know there is a hotel less than 2 km from here but the distance returned is around 6,4 km...But time to eat, will have a look at things afterwards :)

    Very happy about the breaktrough though.

    /Jan

  • Jan Skovgaard 11280 posts 23678 karma points MVP 11x admin c-trib
    Mar 15, 2015 @ 19:49
    Jan Skovgaard
    0

    Hi Anders

    Just had a closer look at the code - Now I get it...you're not even using the Geography data but only looking at the coordinates. Well it seems to be fast. But I don't know if it can have an impact on the accuracy of the distance calculation perhaps?

    I just checked with Google maps - There is a hotel 2.2 km from me but according to the distance calculation it's 6.4 km away...that's a bit too much inaccuracy for the use case. 500 meters +/- would be acceptable...but not sure how to make calculations more exact?

    /Jan

  • Anders Bjerner 487 posts 2989 karma points MVP 8x admin c-trib
    Mar 15, 2015 @ 20:14
    Anders Bjerner
    0

    Hi again,

    Yes, since're the table also has columns for latitude and longitude individually, I thought it was easiest to simply use them instead. So the coordinates field is for searching (unless you use it for something else of course).

    The calculation in the code is only for the hotels being displayed, and it will take less than a millisecond to execute.

    Regarding the distance being wrong, are you able to send me the coordinates? According to my own tests of the logic in Skybrud.Social (which is where I copied it from), it works correctly.

  • Jan Skovgaard 11280 posts 23678 karma points MVP 11x admin c-trib
    Mar 15, 2015 @ 20:21
    Jan Skovgaard
    0

    Hi Anders

    Aaah, that's how it works - Cool and thanks for explaining :)

    I just sent you the coordinates from my place to the hotel I mentioned in DM.

    /Jan

  • Jan Skovgaard 11280 posts 23678 karma points MVP 11x admin c-trib
    Mar 15, 2015 @ 20:35
    Jan Skovgaard
    0

    Here is the data for the hotel in question

    INSERT [dbo].[Hotels] ([Id], [EANHotelID], [Name], [Address1], [City], [CountryCode], [Country], [Latitude], [Longitude], [StarRating], [Confidence], [Location], [Coordinates], [ZipCode]) VALUES (49389, 227116, N'Zleep Hotel Aarhus', N'Viby Ringvej 4', N'Aarhus', N'DK', NULL, 56.12906, 10.16172, N'3.0', N'85', N'Near Tivoli Friheden', 0xE6100000010CF1F44A5986104C40C286A757CA522440, N'DK-8260')
    

    /Jan

  • Jan Skovgaard 11280 posts 23678 karma points MVP 11x admin c-trib
    Mar 15, 2015 @ 20:49
    Jan Skovgaard
    0

    Hi Anders

    Ok, we have found out via Twitter that you get the correct distance of 1.49 km in your setup and I for some reason get a wrong distance of 6.4 km on my setup. So therefore I'm posting my exact code to make sure it's not because I have overlooked something or done something wrong :)

    Here we go...

    Model

    using System;
    using System.Data.Entity.Spatial;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Runtime.Serialization;
    using Microsoft.SqlServer.Types;
    
    
    using Umbraco.Core.Persistence;
    using Umbraco.Core.Persistence.DatabaseAnnotations;
    
    namespace Hotels.Models
    {
        [TableName("Hotels")]
        [DataContract(Name = "hotel")]
        public class Hotel
        {
           // public Hotel(){}
    
            private Location _location;
    
            [PrimaryKeyColumn(AutoIncrement = true)]
            [DataMember(Name = "id")]
            public int Id { get; set; }
    
            [DataMember(Name = "eanHotelId")]
            public int EANHotelID { get; set; }
    
            [DataMember(Name = "name")]
            public string Name { get; set; }
    
            [DataMember(Name = "address1")]
            public string Address1 { get; set; }
    
            [DataMember(Name = "city")]
            public string City { get; set; }
    
            [DataMember(Name = "countryCode")]
            public string CountryCode { get; set; }
    
            [DataMember(Name = "country")]
            public string Country { get; set; }
    
            [DataMember(Name = "latitude")]
            public float Latitude { get; set; }
    
            [DataMember(Name = "longitude")]
            public float Longitude { get; set; }
    
            [DataMember(Name = "starRating")]
            public string StarRating { get; set; }
    
            [DataMember(Name = "confidence")]
            public string Confidence { get; set; }
    
            [DataMember(Name = "location")]
            public string Location { get; set; }
    
            /*[DataMember(Name = "coordinates")]
            public DbGeography Coordinates { get; set; }*/
    
            [DataMember(Name = "zipCode")]
            public string ZipCode { get; set; }
    
            [Ignore]
            public Location Coordinates {
                get { return _location ?? (_location = new Location(Latitude, Longitude)); }
            }
        }
    }
    

    Controller

    sing System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using Hotels.Models;
    using Umbraco.Core;
    using Umbraco.Core.Persistence;
    using Microsoft.SqlServer.Types;
    
    
    namespace Hotels.Controllers
    {
        public class HotelRepository
        {
            private readonly UmbracoDatabase _database;
    
            public HotelRepository() {
                _database = ApplicationContext.Current.DatabaseContext.Database;
            }
    
            public List<Hotel> GetAll(Location location, int radius, int count = 0) {
                SqlGeography point = SqlGeography.Point(location.Longitude, location.Latitude, 4326);
    
                return _database.Fetch<Hotel>(
                    "SELECT " + (count > 0 ? "TOP " + count + " " : "") + "* FROM Hotels WHERE @0.STDistance(Coordinates) <= " + radius + " ORDER BY @0.STDistance(Coordinates) ASC", point.STBuffer(10000)
                    );
            }
    
        }
    }
    

    View

    @using Hotels.Controllers
    @using Hotels.Models
    @inherits Umbraco.Web.Macros.PartialViewMacroPage
    
    @{
        //I fetch some data from a selected venue where to I want to list the 10 nearest hotels
            dynamic venue = Model.Content.GetPropertyValue("venue");
    
            if(!String.IsNullOrEmpty(@venue.ToString())){
                var lat = Convert.ToDouble(venue.latitude);
                var lng = Convert.ToDouble(venue.longitude);
    
            // Set the location we should use for comparison
            Location location = new Location(@lng, @lat);
    
            // Initialize a new instance of the repository
            HotelRepository repo = new HotelRepository();
    
            // Declare the distance (here 5000 meters)
            int distance = 5000;
    
            // Get a list of the 10 nearest hotels
            List<Hotel> hotels = repo.GetAll(location, distance, 10);
    
            // List each hotel
            <table border="1">
                @foreach (var hotel in hotels) {
                    <tr>
                        <td>@hotel.City</td>
                        <td>@hotel.Name</td>
                        <td>@Location.GetDistance(location, hotel.Coordinates) meters</td>
                        <td>@String.Format("{0:N2}", Location.GetDistance(location, hotel.Coordinates)/1000.00) km</td>
                    </tr>
                }
            </table>
    
           }
    
    }
    

    I have not touched the Location class but posting it anyway

    Location.cs

    **

    using System;
    public class Location
    {
        // Based on:
        //   https://github.com/abjerner/Skybrud.Social/blob/master/src/Skybrud.Social/Location.cs
        //   https://github.com/abjerner/Skybrud.Social/blob/master/src/Skybrud.Social/SocialUtils.cs
        #region Properties
        /// <summary>
        /// Gets the latitude of the location. The latitude specifies the north-south position of a
        /// point on the Earth's surface.
        /// </summary>
        public double Latitude { get; set; }
        /// <summary>
        /// Gets the longitude of the location. The longitude specifies the east-west position of a
        /// point on the Earth's surface.
        /// </summary>
        public double Longitude { get; set; }
        #endregion
        #region Constructors
        public Location()
        {
            // Default constructor
        }
        public Location(double latitude, double longitude)
        {
            Latitude = latitude;
            Longitude = longitude;
        }
        #endregion
        #region Static methods
        /// <summary>
        /// Calculates the distance in meters between two GPS locations.
        /// </summary>
        /// <param name="loc1">The first location.</param>
        /// <param name="loc2">The second location.</param>
        public static double GetDistance(Location loc1, Location loc2)
        {
            return GetDistance(loc1.Latitude, loc1.Longitude, loc2.Latitude, loc2.Longitude);
        }
        /// <summary>
        /// Calculates the distance in meters between two GPS locations.
        /// </summary>
        public static double GetDistance(double lat1, double lng1, double lat2, double lng2)
        {
            // http://stackoverflow.com/a/3440123
            double ee = (Math.PI * lat1 / 180);
            double f = (Math.PI * lng1 / 180);
            double g = (Math.PI * lat2 / 180);
            double h = (Math.PI * lng2 / 180);
            double i = (Math.Cos(ee) * Math.Cos(g) * Math.Cos(f) * Math.Cos(h) + Math.Cos(ee) * Math.Sin(f) * Math.Cos(g) * Math.Sin(h) + Math.Sin(ee) * Math.Sin(g));
            double j = (Math.Acos(i));
            return (6371 * j) * 1000d;
        }
        #endregion
    }
    

    /Jan

  • Jan Skovgaard 11280 posts 23678 karma points MVP 11x admin c-trib
    Mar 15, 2015 @ 22:05
    Jan Skovgaard
    0

    Hi Anders

    Thanks for the ping pong on Twitter - I just noticed something in the rendering of the view.

    When I try to write out @hotel.Coordinates I just get a string that says "Location"...I don't know if that's correct? I would expect to see the geography stuff from the database, which looks like this

    But if I change my view to use the coordinates for the hotels in the distance calculations it seems to be more correct - So if I write it like this, then I get a distance of 2.3 KM to the hotel...and Google was giving me 2.2...so that's seems to be fairly accurate, no? :)

    <table border="1">
        @foreach (var hotel in hotels) {
    
         Location hotelLocation = new Location(@hotel.Longitude, @hotel.Latitude);
    
            <tr>
                <td>@hotel.City</td>
                <td>@hotel.Name</td>
                <td>@hotel.Latitude</td>
                <td>@hotel.Longitude</td>
                <td>@Location.GetDistance(location, hotelLocation) meters</td>
                <td>@hotel.Coordinates</td>
                <td>@String.Format("{0:N2}", Location.GetDistance(location, hotelLocation)/1000.00) km</td>
            </tr>
        }
    </table>
    

    Does this make sense?

    /Jan

  • Anders Bjerner 487 posts 2989 karma points MVP 8x admin c-trib
    Mar 16, 2015 @ 00:10
    Anders Bjerner
    0

    Hi Jan,

    That is actually the expected behavior. The Coordinates property is of the type Location. Location is a class, and writing it out on the page like you're doing, will simply result in "Location". The Location class has two properties - Latitude and Longitude both of the type double. You can write these out like @hotel.Coordinates.Latitude and @hotel.Coordinates.Longitude.

    Technically the Coordinates property is constructed from the Latitude and Longitude fields in the database, so it has nothing directly to do with the Coordinates field in the database.

    Regarding your view, it should still do the same whether you do it one way or the other. But I'm too tired to see if that is the case now. I'll have a look at it later with some fresh eyes ;)

Please Sign in or register to post replies

Write your reply to:

Draft