यह लगभग 3 साल पहले बनाए गए ऐप में उपयोग किए जाने वाले फ़ंक्शन का काफी हद तक सरलीकृत संस्करण है। प्रश्न के लिए अनुकूलित।
-
बॉक्स . का उपयोग करके किसी बिंदु की परिधि में स्थान ढूँढता है . अधिक सटीक परिणाम प्राप्त करने के लिए कोई एक मंडली के साथ ऐसा कर सकता है, लेकिन यह केवल आरंभ करने के लिए एक अनुमान के रूप में है।
-
इस तथ्य की उपेक्षा करता है कि संसार समतल नहीं है। मेरा आवेदन केवल एक स्थानीय क्षेत्र के लिए था, जो कि कुछ 100 किलोमीटर के पार था। और खोज परिधि केवल कुछ किलोमीटर के पार फैली हुई है। इस उद्देश्य के लिए दुनिया को सपाट बनाना काफी अच्छा है। (टूडू:भौगोलिक स्थान के आधार पर अक्षांश/देशांतर के अनुपात के लिए एक बेहतर अनुमान मदद कर सकता है।)
-
जियोकोड के साथ काम करता है जैसे आप Google मानचित्र से प्राप्त करते हैं।
-
मानक PostgreSQL के साथ काम करता है बिना एक्सटेंशन के (कोई PostGis आवश्यक नहीं), PostgreSQL 9.1 और 9.2 पर परीक्षण किया गया।
सूचकांक के बिना, किसी को आधार तालिका में प्रत्येक पंक्ति के लिए दूरी की गणना करनी होगी और निकटतम को फ़िल्टर करना होगा। बड़ी टेबल के साथ बेहद महंगा।
संपादित करें:
मैंने फिर से जांच की और वर्तमान कार्यान्वयन बिंदुओं पर एक जिस्ट इंडेक्स की अनुमति देता है (9.1 या बाद के संस्करण पोस्ट करता है)। तदनुसार कोड को सरल बनाया।
<स्ट्राइक>द प्रमुख ट्रिक एक कार्यात्मक बक्से की GiST अनुक्रमणिका का उपयोग करना है , भले ही कॉलम सिर्फ एक बिंदु है। यह मौजूदा GiST कार्यान्वयन का उपयोग करना संभव बनाता है। .स्ट्राइक>
ऐसी (बहुत तेज़) खोज के साथ, हम सभी स्थानों को एक बॉक्स के अंदर प्राप्त कर सकते हैं। शेष समस्या:हम पंक्तियों की संख्या जानते हैं, लेकिन हम उस बॉक्स के आकार को नहीं जानते हैं जिसमें वे हैं। यह उत्तर का हिस्सा जानने जैसा है, लेकिन प्रश्न नहीं।
मैं एक समान रिवर्स-लुकअप . का उपयोग करता हूं dba.SE पर यह संबंधित उत्तर . (केवल, मैं यहां आंशिक अनुक्रमणिका का उपयोग नहीं कर रहा हूं - वास्तव में भी काम कर सकता हूं)।
पूर्व-परिभाषित खोज-चरणों की एक सरणी के माध्यम से, बहुत छोटे से "बस इतना बड़ा कि कम से कम पर्याप्त स्थान धारण करने के लिए"। इसका मतलब है कि खोज बॉक्स के आकार तक पहुंचने के लिए हमें कुछ (बहुत तेज़) क्वेरी चलानी होंगी।
फिर इस बॉक्स के साथ आधार तालिका खोजें और सूचकांक से लौटाई गई केवल कुछ पंक्तियों के लिए वास्तविक दूरी की गणना करें। आमतौर पर कुछ अधिशेष होगा क्योंकि हमें कम से कम . रखने वाला बॉक्स मिला है पर्याप्त स्थान। निकटतम लोगों को लेकर, हम बॉक्स के कोनों को प्रभावी ढंग से गोल करते हैं। आप बॉक्स को एक पायदान बड़ा बनाकर इस प्रभाव को लागू कर सकते हैं (गुणा radius
पूरी तरह से सटीक . प्राप्त करने के लिए sqrt(2) द्वारा फ़ंक्शन में परिणाम, लेकिन मैं पूरी तरह से बाहर नहीं जाऊंगा, क्योंकि यह शुरू होने वाला है)।
यह SP GiST के साथ और भी तेज़ और आसान होगा। index, PostgreSQL के नवीनतम संस्करण में उपलब्ध है। लेकिन मुझे नहीं पता कि यह अभी संभव है या नहीं। हमें डेटा प्रकार के लिए वास्तविक कार्यान्वयन की आवश्यकता होगी और मेरे पास इसमें गोता लगाने का समय नहीं था। अगर आपको कोई रास्ता मिल जाए, तो वापस रिपोर्ट करने का वादा करें!
कुछ उदाहरण मानों के साथ इस सरलीकृत तालिका को देखते हुए (adr
.. पता):
CREATE TABLE adr(adr_id int, adr text, geocode point);
INSERT INTO adr (adr_id, adr, geocode) VALUES
(1, 'adr1', '(48.20117,16.294)'),
(2, 'adr2', '(48.19834,16.302)'),
(3, 'adr3', '(48.19755,16.299)'),
(4, 'adr4', '(48.19727,16.303)'),
(5, 'adr5', '(48.19796,16.304)'),
(6, 'adr6', '(48.19791,16.302)'),
(7, 'adr7', '(48.19813,16.304)'),
(8, 'adr8', '(48.19735,16.299)'),
(9, 'adr9', '(48.19746,16.297)');
सूचकांक इस तरह दिखता है:
CREATE INDEX adr_geocode_gist_idx ON adr USING gist (geocode);
आपको अपनी आवश्यकताओं के अनुसार गृह क्षेत्र, चरणों और स्केलिंग कारक को समायोजित करना होगा। जब तक आप किसी बिंदु के आस-पास कुछ किलोमीटर के बक्सों में खोज करते हैं, तब तक समतल पृथ्वी एक अच्छा पर्याप्त सन्निकटन है।
इसके साथ काम करने के लिए आपको plpgsql को अच्छी तरह समझने की जरूरत है। मुझे लगता है कि मैंने यहां काफी कुछ किया है।
CREATE OR REPLACE FUNCTION f_find_around(_lat double precision, _lon double precision, _limit bigint = 50)
RETURNS TABLE(adr_id int, adr text, distance int) AS
$func$
DECLARE
_homearea CONSTANT box := '(49.05,17.15),(46.35,9.45)'::box; -- box around legal area
-- 100m = 0.0008892 250m, 340m, 450m, 700m,1000m,1500m,2000m,3000m,4500m,7000m
_steps CONSTANT real[] := '{0.0022,0.003,0.004,0.006,0.009,0.013,0.018,0.027,0.040,0.062}'; -- find optimum _steps by experimenting
geo2m CONSTANT integer := 73500; -- ratio geocode(lon) to meter (found by trial & error with google maps)
lat2lon CONSTANT real := 1.53; -- ratio lon/lat (lat is worth more; found by trial & error with google maps in (Vienna)
_radius real; -- final search radius
_area box; -- box to search in
_count bigint := 0; -- count rows
_point point := point($1,$2); -- center of search
_scalepoint point := point($1 * lat2lon, $2); -- lat scaled to adjust
BEGIN
-- Optimize _radius
IF (_point <@ _homearea) THEN
FOREACH _radius IN ARRAY _steps LOOP
SELECT INTO _count count(*) FROM adr a
WHERE a.geocode <@ box(point($1 - _radius, $2 - _radius * lat2lon)
, point($1 + _radius, $2 + _radius * lat2lon));
EXIT WHEN _count >= _limit;
END LOOP;
END IF;
IF _count = 0 THEN -- nothing found or not in legal area
EXIT;
ELSE
IF _radius IS NULL THEN
_radius := _steps[array_upper(_steps,1)]; -- max. _radius
END IF;
_area := box(point($1 - _radius, $2 - _radius * lat2lon)
, point($1 + _radius, $2 + _radius * lat2lon));
END IF;
RETURN QUERY
SELECT a.adr_id
,a.adr
,((point (a.geocode[0] * lat2lon, a.geocode[1]) <-> _scalepoint) * geo2m)::int4 AS distance
FROM adr a
WHERE a.geocode <@ _area
ORDER BY distance, a.adr, a.adr_id
LIMIT _limit;
END
$func$ LANGUAGE plpgsql;
कॉल करें:
SELECT * FROM f_find_around (48.2, 16.3, 20);
$3
. की सूची लौटाता है स्थान, यदि परिभाषित अधिकतम खोज क्षेत्र में पर्याप्त हैं।
वास्तविक दूरी के आधार पर क्रमबद्ध।
आगे सुधार
एक फ़ंक्शन बनाएं जैसे:
CREATE OR REPLACE FUNCTION f_geo2m(double precision, double precision)
RETURNS point AS
$BODY$
SELECT point($1 * 111200, $2 * 111400 * cos(radians($1)));
$BODY$
LANGUAGE sql IMMUTABLE;
COMMENT ON FUNCTION f_geo2m(double precision, double precision)
IS 'Project geocode to approximate metric coordinates.
SELECT f_geo2m(48.20872, 16.37263) --';
(शाब्दिक रूप से) वैश्विक स्थिरांक 111200
और 111400
देशांतर की एक डिग्री की लंबाई
से मेरे क्षेत्र (ऑस्ट्रिया) के लिए अनुकूलित हैं और अक्षांश की एक डिग्री की लंबाई
, लेकिन मूल रूप से पूरी दुनिया में काम करते हैं।
आधार तालिका में एक स्केल किए गए जियोकोड को जोड़ने के लिए इसका उपयोग करें, आदर्श रूप से एक जेनरेट किया गया कॉलम जैसा कि इस उत्तर में उल्लिखित है:
आप तारीख का गणित कैसे करते हैं जो साल को नज़रअंदाज़ कर देता है?
देखें 3. काला जादू संस्करण जहां मैं आपको प्रक्रिया के बारे में बताता हूं।
फिर आप फ़ंक्शन को कुछ और सरल कर सकते हैं:इनपुट मानों को एक बार स्केल करें और अनावश्यक गणनाओं को हटा दें।