ããã«ã¡ã¯ãHabrïŒ
ç§ã®ååã¯ãã«ã³ã§ãããã©ãããã©ãŒã ããŒã ã®
Badooã§åããŠããŸãã å°ãåã®
GopherCon Russia 2018ã§ ã座æšã®æäœæ¹æ³ã«ã€ããŠè©±ããŸããã ãããªãèŠãã®ãå«ããªäººïŒãããŠãã¡ããèå³ã®ãã人ïŒã®ããã«ãã¬ããŒãã®ããã¹ãçãå
¬éããŠããŸãã

ã¯ããã«
çŸåšãäžçäžã®ã»ãšãã©ã®äººã¯ãåžžæã€ã³ã¿ãŒãããã«ã¢ã¯ã»ã¹ã§ããã¹ããŒããã©ã³ãæã£ãŠããŸãã æ°åã§èšãã°ã2018幎ã«ã¯çŽ50å人
ãã¹ããŒããã©ã³ã
䜿çšã ããã®ãã¡60ïŒ
ãã¢ãã€ã«ã€ã³ã¿ãŒãããã
䜿çšããŸãã
ãããã¯èšå€§ãªæ°ã§ãã äŒæ¥ããŠãŒã¶ãŒåº§æšãååŸããããšã¯ç°¡åã«ãªããŸããã ãããã®äœ¿ãããããšã¢ã¯ã»ã·ããªãã£ã¯ã座æšã«åºã¥ããŠèšå€§ãªæ°ã®ãµãŒãã¹ãçã¿åºããŸããïŒãããŠçã¿åºãç¶ããŠããŸãïŒã
Ingressã
Pokemon Goãªã©ãäžçãåŸæãã
Uberã®ãããªäŒæ¥ãç¥ã£ãŠããŸãã ããããéè¡ã®ã¢ããªã±ãŒã·ã§ã³ã§ã¯ãè¿ãã«ATMãå²åŒãèŠãæ©äŒããããŸãã
Badooã§ã¯ã座æšãéåžžã«ç©æ¥µçã«äœ¿çšããŠããŠãŒã¶ãŒã«æé©ã§é¢é£æ§ã®é«ãè峿·±ããµãŒãã¹ãæäŸããŠããŸãã ããããã©ã®ãããªçšéã«ã€ããŠè©±ããŠããã®ã§ããããïŒ ç§ãã¡ãæã£ãŠãããµãŒãã¹ã®äŸãèŠãŠã¿ãŸãããã
Badooã®ãžãªãµãŒãã¹
æåã®äŸïŒMeetmakerãšGeousers
Badooã¯äž»ã«ããŒãã§ãã 人ã
ããäºããç¥ãåãå Žæã äººãæ€çŽ¢ããéã®éèŠãªåºæºã®1ã€ã¯ãè·é¢ã§ãã å®éãã»ãšãã©ã®å Žåãã¢ã¹ã¯ã¯ã®å°å¥³ã¯ãé ãé¢ãããŠã©ãžãªã¹ããã¯ã§ã¯ãªããå°ãªããšãã¢ã¹ã¯ã¯ã«äœãã§ããããå°ãªããšãã¢ã¹ã¯ã¯ã«äœãã§ããå°å¹Žã«äŒãããã
ãã¯ãããŸãã¯ãããããã§ãåçããŸãã¯ãŠãŒã¶ãŒãæ¡å
ãããŠãŒã¶ãŒãéžæããç¹å®ã®ååŸå
ã§ããªãã«åã£ãåºæºãæã€ãŠãŒã¶ãŒãæ€çŽ¢ãããµãŒãã¹ã
2çªç®ã®äŸïŒGeoborder
ãŠãŒã¶ãŒãã©ã®éœåžã«ããã®ããã©ã®åœã«ããã®ããããæ£ç¢ºã«ã¯ãããšãã°ã©ã®ç©ºæž¯ã«ãã©ã®å€§åŠã«ããã®ããšãã質åã«çããããã«ããããã
ãžãªãã§ã³ã·ã³ã°ãæ±ã
ãžãªããŒããŒãµãŒãã¹ã䜿çšããŸãã
ãããã®è³ªåã«çããæãç°¡åãªæ¹æ³ã¯ããŠãŒã¶ãŒããåžå
äžå¿éšãŸãã¯å€§åŠã®äžå¿ãŸã§ã®è·é¢ãèæ
®ããããšã§ããããã®ã¢ãããŒãã¯éåžžã«äžæ£ç¢ºã§ããã倿°ã®å¢çæ¡ä»¶ã«äŸåããŸãã
ãã®ãããåœãéœåžãéèŠãªãªããžã§ã¯ãïŒããšãã°ãç§ã話ãã倧åŠã空枯ãªã©ïŒã®éåžžã«æ£ç¢ºãªå¢çç·ãæããŸããã ãããã®å¢çã¯ããªãŽã³ã«ãã£ãŠèšå®ãããŸãã ãã®ãããªå¢çã®ã»ããããããšããŠãŒã¶ãŒãããªãŽã³ã®å
åŽã«ãããã©ããããŸãã¯ããªãŽã³ã«æãè¿ãããªãŽã³ãèŠã€ããããšãã§ããŸãã ãããã£ãŠãã©ã®éœåžã«ããããèšãããšããæãè¿ãéœåžãèŠã€ããããšãã§ããŸãã
3çªç®ã®äŸïŒBumpd
Bumped IntoãšåŒã°ããéåžžã«äººæ°ã®ããæ©èœããããŸããããã¯ãããã¯ã°ã©ãŠã³ãã§æéãšç©ºéã§äº€å·®ãããŠãŒã¶ãŒãåžžã«æ€çŽ¢ãããã®ç·ã®åãŸãã¯ãã®å¥³ã®åãšåãå Žæã«å®æçã«ã¢ã¯ã»ã¹ããããšã瀺ããŸãã宿çã«åãéãé²ã¿ãŸãã ãã®æ©èœã¯ãäŒè©±ãéå§ã§ãããããã¯ã«ç²Ÿéãããã1ã€ã®çç±ã§ããããããŠãŒã¶ãŒã«éåžžã«äººæ°ããããŸãã
4çªç®ã®äŸïŒGeocache-ãååŸãããã®ã«è²»çšããããå°çæ
å ±ã®ãã£ãã·ã¥
ããŠãç§ãèšåããæåŸã®äŸã¯ãå°çæ
å ±ã®ãã£ãã·ã³ã°ã«é¢é£ããŠããŸãã ããšãã°ãBooking.comã®ããŒã¿ã䜿çšãããšãåšãã®ããã«ã«é¢ããæ
å ±ãæäŸãããŸãããæ¯åBooking.comã«ã¢ã¯ã»ã¹ããã«ã¯é·ãããŸãã ãã®ãã€ãã®ç©Žã®ããã«ãããªãã®ã€ã³ã¿ãŒããããã€ãã¯ããããããªãçãã§ãããã ãããããããŒã¿ã®ããã«ã¢ã¯ã»ã¹ãããµãŒãã¹ã¯ããªã¯ãšã¹ãããšã«ãéããããã®ã§ãå°ãç¯çŽãããã§ãããã

ãã®å Žåããã£ãã·ã³ã°ã¬ã€ã€ãŒããããšäŸ¿å©ã§ãããã£ãã·ã³ã°ã¬ã€ã€ãŒã¯ãäœéãŸãã¯é«äŸ¡ãªãµãŒãã¹ã®ãªã¯ãšã¹ãæ°ã倧å¹
ã«åæžããå€éšã«éåžžã«é¡äŒŒããAPIãæäŸããŸãã ãã®ãšãªã¢ã®ãã¹ãŠãŸãã¯ã»ãšãã©ã®ããã«ã«ã€ããŠåœŒããã§ã«ç¥ã£ãŠããããšãçè§£ãããµãŒãã¹ããã®ããŒã¿ã¯æ¯èŒçæ°ããããããã£ãŠãå€éšãµãŒãã¹ã§ããã«å
¥ãããšã¯ã§ããŸããã ãã®ãããªã¿ã¹ã¯ã¯ãGeocacheãšåŒã°ãããµãŒãã¹ã«ãã£ãŠè§£æ±ºãããŸãã
ãžãªãµãŒãã¹ã®æ©èœ
å€ãã®åº§æšãããã座æšãéèŠã§ããããããã«åºã¥ããŠå€ãã®è峿·±ããµãŒãã¹ãäœæã§ããããšããã§ã«èªèããŠããŸãã ããã§ã¯ã次ã¯äœã§ããïŒ å®éãåé¡ã¯äœã§ããïŒ åº§æšã¯ããŠãŒã¶ãŒããåãåã£ãä»ã®æ
å ±ãšã©ã®ããã«ç°ãªããŸããïŒ
ãããŠã2ã€ã®æ©èœããããŸãã

ãŸããå°çããŒã¿ã¯3次å
ããŸãã¯2次å
ã§ããããã¯ãäžè¬çãªå Žåãé«ãã«é¢å¿ããªãããã§ãã ãããã¯ç·¯åºŠãšçµåºŠã§æ§æãããã»ãšãã©ã®å Žåãäž¡æ¹ã§åæã«æ€çŽ¢ãè¡ãããŸãã ãšãªã¢ã§æ€çŽ¢ãæ³åããŠãã ããã äžè¬çãªDBMSã®æšæºã€ã³ããã¯ã¹ã¯ããã®ãããªäœ¿çšã«ã¯æé©ã§ã¯ãããŸããã

第äºã«ãå€ãã®ã¿ã¹ã¯ã«ã¯ãããªãŽã³ãªã©ã®ããè€éãªããŒã¿ã¿ã€ããå¿
èŠã§ããããã«ã€ããŠã¯ãBadooã®GeoborderãµãŒãã¹ã®äŸã§èª¬æããŸããã ãã¡ãããããªãŽã³ã¯é ç¹åº§æšã§æ§æãããŸããã远å ã®åŠçãå¿
èŠã§ããããããã®ã¿ã€ãã®ããŒã¿ã«å¯Ÿããæ€çŽ¢ã¯ãšãªãè€éã§ãã ã¯ãšãªããã€ã³ãã«æãè¿ãããªãŽã³ãæ€ââ玢ããããŸãã¯ãç¹å®ã®ãã€ã³ããå«ãããªãŽã³ãæ€ââ玢ãããããã§ã«èŠãŠããŸããã
ãµãŒãã¹ãæžãçç±
ãããã®æ©èœã«æºæ ããããã«ãå€ãã®DBMSã«ã¯ã倿¬¡å
ããŒã¿çš
ã«ã·ã£ãŒãåããã
ç¹å¥ãªã€ã³ããã¯ã¹ ãããã³ããªãŽã³ãããªã©ã€ã³ãªã©ã®ãªããžã§ã¯ããæäœã§ããè¿œå æ©èœãå«ãŸããŠããŸãã
ããããæãé¡èãªäŸã¯
PostGISã§ã ãããã¯ãäžè¬çãª
PostgreSQL DBMSã®æ¡åŒµæ©èœã§ããã座æšãšå°çãæäœããããã®æãåºãå¯èœæ§ãæã£ãŠããŸãã
ãã ããæ¢è£œã®DBMSã䜿çšããããšãå¯äžã®ãœãªã¥ãŒã·ã§ã³ã§ã¯ãªããåžžã«æè¯ã®ãœãªã¥ãŒã·ã§ã³ã§ã¯ãããŸããã
ãµãŒãã¹ãšDBMSãšã®éã§ãµãŒãã¹ã®ããžãã¹ããžãã¯ãå
±æããããªãå ŽåãDBMSã§å©çšã§ããªããã®ãå¿
èŠãªå ŽåããµãŒãã¹ãå®å
šã«ç®¡çãããå ŽåãDBMSãã¹ã±ãŒãªã³ã°ããæ©èœã«å¶éãããªãå Žåã¯ããµãŒãã¹ã«å°çããŒã¿ãæ€çŽ¢ããã³æäœããæ©èœãçµã¿èŸŒã¿ããã
ãã®ã¢ãããŒãã¯ééããªãæè»æ§ããããŸãããDBMSã¯ãªãŒã«ã€ã³ã¯ã³ãœãªã¥ãŒã·ã§ã³ã§ãããã¬ããªã±ãŒã·ã§ã³ãªã©ã®å€ãã®ã€ã³ãã©ã¹ãã©ã¯ãã£ãæ¢ã«è¡ãããŠãããããã¯ããã«è€éã«ãªãå¯èœæ§ããããŸãã ã¬ããªã±ãŒã·ã§ã³ããããŠå®éã«ã¯ã座æšãæäœããããã®ã¢ã«ãŽãªãºã ãšã€ã³ããã¯ã¹ã
ãããããã¹ãŠãããã»ã©æãããã§ã¯ãããŸããã å¿
èŠãªãã®ã®ã»ãšãã©ãå®è£
ããã©ã€ãã©ãªã玹ä»ããããšæããŸãã ããã¯äžçš®ã®ç«æ¹äœã§ãçäœã®ãžãªã¡ããªãæäœãããã座æšã§æ€çŽ¢ãããããå¿
èŠãããå Žæã«ç°¡åã«çµã¿èŸŒãŸããŸãã
S2ãšããã©ã€ãã©ãªã䜿çšããŸãã
S2
S2ã¯ããžãªã¡ããªïŒçäœãå«ãïŒãæäœããããã®ã©ã€ãã©ãªã§ããããžãªã€ã³ããã¯ã¹ãäœæããããã®éåžžã«äŸ¿å©ãªæ©èœãæäŸããŸãã
æè¿ãŸã§ãS2ã¯ã»ãšãã©ç¥ãããŠããããããã¥ã¡ã³ããéåžžã«è²§åŒ±ã§ããããGoogleã¯ãªãŒãã³ãœãŒã¹ã§ã
åãªãªãŒã¹ ãããããšã決å®ãã補åã®ãµããŒããšéçºãçŽæããŠã¬ã€ã¢ãŠãã«è¿œå ããŸããã
ã©ã€ãã©ãªã®ã¡ã€ã³ããŒãžã§ã³ã¯C ++ã§èšè¿°ãããŠãããGoããŒãžã§ã³ãå«ãä»ã®èšèªã®å
¬åŒããŒããŸãã¯ããŒãžã§ã³ãããã€ããããŸãã çŸåšãGoããŒãžã§ã³ã¯C ++ããŒãžã§ã³ã®ãã¹ãŠã100ïŒ
å®è£
ããŠããããã§ã¯ãããŸããããã»ãšãã©ã®ãã®ãå®è£
ããã«ã¯ååã§ãã
Googleã«å ããŠããã®ã©ã€ãã©ãªã¯
Foursquare ã
Uber ã
Yelp ããããŠãã¡ãã
Badooãªã©ã®äŒæ¥ã§ãç©æ¥µçã«äœ¿çšãããŠããŸãã ãŸããã©ã€ãã©ãªãå
éšã§äœ¿çšãã補åã®äžã§ãããç¥ãããŠããMongoDBããã¹ãŠã®ãŠãŒã¶ãŒã«ã¢ããŒã«ã§ããŸãã
ãããããããŸã§ã®ãšãããS2ã䟿å©ãªçç±ãšããžãªãµãŒãã䜿çšããŠãµãŒãã¹ãç°¡åã«äœæã§ããçç±ã«ã€ããŠå®çšçãªããšã¯äœãèšããŸããã§ããã 2ã€ã®äŸãèŠãåã«ãèªåèªèº«ãä¿®æ£ããå
éšãå°ãæãäžããŠã¿ãŸãããã
æåœ±
éåžžãå°çåŠã§ã¯ãå¹³é¢äžã§æãäžè¬çãªå°çã®æåœ±ã®1ã€ã䜿çšããŸãã ããšãã°ã
ã¡ã«ã«ãã«å³æ³ã¯ãã¹ãŠç¥ã£ãŠããŸãã ãã®ã¢ãããŒãã®æ¬ ç¹ã¯ãæåœ±ã«äœããã®åœ¢ã§æªã¿ãããããšã§ãã ç§ãã¡ã®å°çã¯é£è¡æ©ã«ããŸã䌌ãŠããŸããã

ãã·ã¢ãšã¢ããªã«ãæ¯èŒããæåãªåçãæãåºããŠãã ããã ãã·ã¢ã¯å°å³äžã§ã¯å·šå€§ã§ãããå®éãã¢ããªã«ã®é¢ç©ã¯ãã§ã«ãã·ã¢ã®é¢ç©ã®2åã§ãïŒ ããã¯ã¡ã«ã«ãã«å³æ³ã®æªã¿ã®äŸã«ãããŸããã
S2ã¯ãçé¢æåœ±ãšçé¢ãžãªã¡ããªã®ã¿ã䜿çšããŠãã®åé¡ã解決ããŸãã ãã¡ãããå°çãå®å
šãªçäœã§ã¯ãããŸããããçäœã®å Žåã®æªã¿ã¯ã»ãšãã©ã®ã¿ã¹ã¯ã§ç¡èŠã§ããŸãã
åäž
çãŸãã¯
åäœç ãã€ãŸãååŸ1ã®çã§äœæ¥ãã
äžå¿è§ ãç圢ã®é·æ¹åœ¢ã
ç圢ã®ã»ã°ã¡ã³ããªã©ã®æŠå¿µã䜿çšã
ãŸã ã
S2ãšããååã¯ãåäœçãç€ºãæ°åŠè¡šèšã«ç±æ¥ããŠããŸãã
ããããã»ãšãã©ãã¹ãŠã®æ°åŠã峿žé€šã«åŒãç¶ãããŠããã®ã§ãæãã£ãŠã¯ãããŸããã
éå±€ã»ã«
ã©ã€ãã©ãªã®2çªç®ã®ïŒãããŠæãéèŠãªïŒæ©èœã¯ãã°ããŒããã»ã«ïŒè±èª-ã»ã«ïŒã«éå±€çã«åå²ãããšããæŠå¿µã§ãã
ããŒãã£ã·ã§ã³ã¯éå±€çã§ããã€ãŸããã»ã«ã®ã¬ãã«ïŒãŸãã¯ã¬ãã«ïŒãªã©ããããŸãã ãããŠãããã¬ãã«ã§ã¯ãã»ã«ã¯ã»ãŒåããµã€ãºã§ãã
ã»ã«ã¯å°çäžã®ä»»æã®ãã€ã³ããèšå®ã§ããŸãã å¹
ã1ã»ã³ãã¡ãŒãã«æªæºã®ãµã€ãºã®æå€§30ã¬ãã«ã®ã»ã«ã䜿çšãããšããåãã®ããã«ç²ŸåºŠãéåžžã«é«ããªããŸãã äžäœã¬ãã«ã®ã»ã«ã¯åããã€ã³ããèšå®ã§ããŸããã粟床ã¯ãã§ã«äœããªããŸãã ããšãã°ã5 m x 5 mã
ã»ã«ã¯ããã€ã³ãã ãã§ãªããããªãŽã³ãäžéšã®é åãªã©ã®ããè€éãªãªããžã§ã¯ããæå®ã§ããŸãïŒåçã§ã¯ãããšãã°ãã¯ã€ã衚瀺ãããŠããŸãïŒã ãã®å Žåããã®ãããªæ°å€ã¯ã»ã«ã®ã»ããïŒããããç°ãªãã¬ãã«ïŒã«ãã£ãŠå®çŸ©ãããŸãã
ãã®ãããªããŒãã£ã·ã§ã³ã¯éåžžã«ã³ã³ãã¯ãã§ããåã»ã«ã¯1ã€ã®64ãããæ°ã§ãšã³ã³ãŒãã§ããŸãã
ãã«ãã«ãæ²ç·
ã»ã«ã«ã¯åäžã®64ãããæ°ãäžãããããšè¿°ã¹ãŸããã ããã«ããïŒ å°çäžã®åº§æšãŸãã¯ãã€ã³ãã¯ãããã©ã«ãã§ã¯2ã€ã®åº§æšã§èšå®ãããŸãããæšæºã®æ¹æ³ã§äœæ¥ããã«ã¯äžäŸ¿ã§ããããã1ã€ã®æ°å€ã§æå®ã§ããŸãã ãããããããéæããæ¹æ³ã¯ïŒ èŠãŠã¿ãŸããã...
çäœã®éå±€ããŒãã£ã·ã§ã³ã¯ã©ã®ããã«çºçããŸããïŒ
æåã«çäœãç«æ¹äœã§å²ã¿ã6ã€ã®é¢ãã¹ãŠã«æåœ±ãããã®å Žã§æåœ±ã埮調æŽããŠæªã¿ãé€å»ããŸãã ããã¯ã¬ãã«0ã§ããæ¬¡ã«ãç«æ¹äœã®6ã€ã®é¢ã®ããããã4ã€ã®çããéšåã«åå²ã§ããŸã-ããã¯ã¬ãã«1ã§ãããŸããçµæã®4ã€ã®éšåã¯ãããã4ã€ã®çããéšå-ã¬ãã«2ã«åå²ã§ããŸãã
ãããããã®æ®µéã§ã¯ããŸã äºæ¬¡å
æ§ããããŸãã ãããŠãããã§é ãéå»ããã®æ°åŠçã¢ã€ãã¢ãæãã«æ¥ãŸãã 19äžçŽã®çµããã«ãDavid Hilbertã¯ãå転ãæãç³ã¿ããããã£ãŠç©ºéãåãã1次å
ã®ç·ã§ç©ºéãåããæ¹æ³ãæãã€ããŸããã
ãã«ãã«ãæ²ç·ã¯ååž°çã§ããããã粟床ãŸãã¯å¯åºŠãéžæã§ããŸãã å¿
èŠã«å¿ããŠãå°ããªããŒã¹ãããå¯ã«å
å¡«ã§ããŸãã

ãã®ãããªæ²ç·ã§2次å
空éãåããããšãã§ããŸãã ãããŠä»ããã®æ²ç·ãåãããããçŽç·ã«åŒã䌞ã°ããšïŒæååãåŒã£åŒµã£ãŠããããã«ïŒã倿¬¡å
ãªããžã§ã¯ããèšè¿°ãã1次å
ã®ãªããžã§ã¯ããååŸããŸã-ãã®ç·äžã®1次å
ã®ãªããžã§ã¯ããŸãã¯åº§æšã¯ã䟿å©ã§æ±ããããã§ãã
äžå€®ã¬ãã«ã®1ã€ã§ã®å°çã®ãã«ãã«ãæ²ç·ã®å¡ãã€ã¶ãã¯ã次ã®ããã«ãªããŸãã
ãã«ãã«ãæ²ç·ã®ãã1ã€ã®ç¹åŸŽã¯ãæ²ç·ã®è¿ãã«ãããã€ã³ããè¿ãã«ããã空éã«ãããšããäºå®ã§ãã ãã®éã¯åžžã«ã§ã¯ãããŸããããåºæ¬çã«ã¯çå®ã§ãã ãŸãããã®æ©èœã¯ç©æ¥µçã«é©çšãããŸãã
64ããã倿°ãžã®ãšã³ã³ãŒã
ãããããªããã»ã«IDãšåŒã°ããåäžã®64ãããæ°ã§ã»ã«ããšã³ã³ãŒãã§ããã®ã§ããããïŒ èŠãŠã¿ãŸãããã
ç«æ¹äœã®6ã€ã®é¢ããããŸãã 6ã€ã®ç°ãªãå€ã3ãããã§ãšã³ã³ãŒãã§ããŸãã
察æ°ãèŠããŠãããŠãã ããã
次ã«ã6ã€ã®é¢ã®ããããã30å4ã€ã®çããéšåã«åå²ããŸãã åã¬ãã«ã§ã»ã«ãåå²ãã4ã€ã®éšåã®1ã€ãèšå®ããã«ã¯ã2ãããã§ååã§ãã
åèš3 + 2 * 30 =63ããããŠãã»ã«ãCellIDã«ãã£ãŠäžããããã¬ãã«ãç¥ããçŽ æ©ãçè§£ããããã«ãæåŸã«1ãããã远å ããŸãã
ããããã¹ãŠã®ããŒãã£ã·ã§ã³ãš1ã€ã®æ°å€ã§ã®ãã«ãã«ãæ²ç·ã®ã³ãŒãã£ã³ã°ã¯äœãããããã®ã§ããããïŒ
- å°çäžã®ä»»æã®ãã€ã³ãã1ã€ã®CellIDã§ãšã³ã³ãŒãã§ããŸãã ã¬ãã«ã«å¿ããŠãç°ãªã粟床ãåŸãããŸãã
- å°çäžã®ä»»æã®2次å
ãªããžã§ã¯ãã1ã€ä»¥äžã®CellIDããšã³ã³ãŒãã§ããŸãã åæ§ã«ãã¬ãã«ãå€ããããšã§æ£ç¢ºã«éã¶ããšãã§ããŸãã
- ãã«ãã«ãæ²ç·ã®ããããã£ã¯ããã®é£ã«ãããã€ã³ããè¿ãã«ããã空éã«ããããšãããã³CellIDãåãªãæ°åã§ãããšããäºå®ã«ãããBããªãŒã¿ã€ãã®éåžžã®ããªãŒãæ€çŽ¢ã«äœ¿çšã§ããŸãã æ¢ããŠãããã®ïŒãã€ã³ããŸãã¯ãªãŒãžã§ã³ïŒã«å¿ããŠãgetãŸãã¯rangeã¹ãã£ã³ãã€ãŸããtoãšfromã®æ€çŽ¢ã䜿çšããŸãã
- å°çãã¬ãã«ã«åå²ãããšãã·ã¹ãã ãã·ã£ãŒãã£ã³ã°ããããã®ã·ã³ãã«ãªãã¬ãŒã ã¯ãŒã¯ãåŸãããŸãã ããšãã°ããŒãã¬ãã«ã§ã¯ããµãŒãã¹ãæåã®ã¬ãã«ïŒ24ãªã©ïŒã§6ã€ã®ã€ã³ã¹ã¿ã³ã¹ã«åå²ã§ããŸãã
ããã§ããã®çè«çç¥èãçµ±åããããã«ã2ã€ã®äŸãèŠãŠã¿ãŸãããã 2ã€ã®ç°¡åãªãµãŒãã¹ãäœæããŸãããæåã®ãµãŒãã¹ã¯æ€çŽ¢ã®ã¿ã§ãã
äŸ
ç§ãã¡ã¯åšãã®äººãèŠã€ããããã®ãµãŒãã¹ãæžããŸã
æ€çŽ¢ã€ã³ããã¯ã¹ãäœæããŸãã ã€ã³ããã¯ã¹ãäœæãã颿°ãã€ã³ããã¯ã¹ã«äººã远å ãã颿°ããããŠå®éã«ã¯æ€çޢ颿°ãå¿
èŠã§ãã ããŠããã¹ãã
type Index struct {} func NewIndex(storageLevel int) *Index {} func (i *Index) AddUser(userID uint32, lon, lat float64) error {} func (i *Index) Search(lon, lat float64, radius uint32) ([]uint32, error) {} func TestSearch(t *testing.T) {}
ãŠãŒã¶ãŒã¯IDãšåº§æšã«ãã£ãŠèšå®ãããæ€çŽ¢ã¯æ€çŽ¢ã»ã³ã¿ãŒã®åº§æšãšæ€çŽ¢ååŸã«ãã£ãŠèšå®ãããŸãã
ã€ã³ããã¯ã¹ã§ã¯ã
BããªãŒãå¿
èŠã§ããããŒãã®storageLevelã¬ãã«ã®ã»ã«ãšããã®ã»ã«ã®ãŠãŒã¶ãŒã®ãªã¹ããæ ŒçŽããŸãã storageLevelã»ã«ã¬ãã«ã¯ãåææ€çŽ¢ã®ç²ŸåºŠãšããã©ãŒãã³ã¹ã®éã®éžæã§ãã ãã®äŸã§ã¯ã1 kmãããçŽ1 kmã®ã»ã«ãµã€ãºã䜿çšããŸãã Less颿°ã¯ãBããªãŒãæ©èœããããã«å¿
èŠã§ãããã®ãããããªãŒã¯ãã©ã®å€ãå°ãããã倧ããããããªãŒãçè§£ã§ããŸãã
type Index struct { storageLevel int bt *btree.BTree } type userList struct { cellID s2.CellID list []uint32 } func (ul userList) Less(than btree.Item) bool { return uint64(ul.cellID) < uint64(than.(userList).cellID) }
次ã«ããŠãŒã¶ãŒã®è¿œå æ©èœãèŠãŠã¿ãŸãããã
func (i *Index) AddUser(userID uint32, lon, lat float64) error { latlng := s2.LatLngFromDegrees(lat, lon) cellID := s2.CellIDFromLatLng(latlng) cellIDOnStorageLevel := cellID.Parent(i.storageLevel)

ããã§ã¯ãæåã«S2ã®åäœã確èªããŸãã åºŠã§æå®ããã座æšãã©ãžã¢ã³ã«å€æããŠãããæå€§30ã¬ãã«ïŒã€ãŸããæå°ã»ã«ãµã€ãºïŒã®ãããã®åº§æšã«å¯Ÿå¿ããCellIDã«å€æããçµæã®CellIDãã¬ãã«ã®CellIDã«å€æããŸãã人ã
ãä¿ã¡ãŸãã ãã®é¢æ°ã®ãå
åŽããèŠããšãæ°ãããããŒãã«ãªãã ãã§ãã CellIDã®ãšã³ã³ãŒãæ¹æ³ãèŠããŠãããŠãã ããã
func (i *Index) AddUser(userID uint32, lon, lat float64) error {
次ã«ããã®ãŠãŒã¶ãŒãBããªãŒã®é©åãªã»ã«ã«è¿œå ããã ãã§ãã ãŸãã¯ã誰ããæ¢ã«ããã«ããå Žåã¯ãæåã«ããŸãã¯ãªã¹ãã®æåŸã«ã ç°¡åãªã€ã³ããã¯ã¹ã®æºåãã§ããŸããã
æ€çŽ¢æ©èœã«ç§»åããŸãã æåã®å€æã¯äŒŒãŠããŸãããã»ã«ã§ã¯ãªãã座æšã§ãã€ã³ããªããžã§ã¯ããååŸããŸãã ãããæ€çŽ¢ã®äžå¿ã§ãã
func (i *Index) Search(lon, lat float64, radius uint32) ([]uint32, error) { latlng := s2.LatLngFromDegrees(lat, lon) centerPoint := s2.PointFromLatLng(latlng) centerAngle := float64(radius) / EarthRadiusM cap := s2.CapFromCenterAngle(centerPoint, s1.Angle(centerAngle))
ããã«ã¡ãŒãã«åäœã®ååŸã«æ²¿ã£ãŠãäžå¿è§ãååŸããŸãã ããã¯ãçã®äžå¿ããæ¥ãè§åºŠã§ãã ãã®å Žåã®å€æã¯åçŽã§ããã¡ãŒãã«åäœã®å°çã®ååŸãã¡ãŒãã«åäœã®å°çã®ååŸã§å²ãã°ååã§ãã
æ€çŽ¢ã®äžå¿ç¹ãšååŸããè§åºŠã«ãã£ãŠãç圢ã®ã»ã°ã¡ã³ãïŒãŸãã¯ãåžœåãïŒãèšç®ã§ããŸãã å®éãããã¯çäžã®åã§ããã3次å
ã®ã¿ã§ãã
ããŠãå
éšã«èŠãå¿
èŠããããåžœåãããããŸãã ããããã©ã®ããã«ïŒ ãããè¡ãã«ã¯ãS2ã«ããµãŒã¯ã«ãŸãã¯ããã£ããããå®å
šã«ã«ããŒããstorageLevelã¬ãã«ã®ã»ã«ã®ãªã¹ããæäŸããããã«äŸé ŒããŸãã
func (i *Index) Search(lon, lat float64, radius uint32) ([]uint32, error) {
次ã®ããã«ãªããŸãã
åä¿¡ããã»ã«ãééããBããªãŒã§get-sããŠãå
éšã«ãããã¹ãŠã®ãŠãŒã¶ãŒãååŸããã ãã§ãã
func (i *Index) Search(lon, lat float64, radius uint32) ([]uint32, error) {
ãã¹ãã®ããã«ãã€ã³ããã¯ã¹ãšè€æ°ã®ãŠãŒã¶ãŒãäœæããŸãã 3è¿ãã2ã€é¢ããŠããŸãã ãŸããååŸãå°ããå Žåã¯3人ã®ãŠãŒã¶ãŒã®ã¿ãæ»ããååŸã倧ããå Žåã¯ããã ãã§ããããšã確èªããŸãã ãã£ãïŒ ç§ãã¡ã®ç°¡åãªå°ããªããã°ã©ã ã¯åäœããŸãã
func prepare() *Index { i := NewIndex(13 ) i.AddUser(1, 14.1313, 14.1313) i.AddUser(2, 14.1314, 14.1314) i.AddUser(3, 14.1311, 14.1311) i.AddUser(10, 14.2313, 14.2313) i.AddUser(11, 14.0313, 14.0313) return i } func TestSearch(t *testing.T) { indx := prepare() found, _ := indx.Search(14.1313, 14.1313, 2000) if len(found) != 3 { t.Fatal("error while searching with radius 2000") } found, _ = indx.Search(14.1313, 14.1313, 20000) if len(found) != 5 { t.Fatal("error while searching with radius 20000") } } $ go test -run 'TestSearch$' PASS ok github.com/mkevac/gophercon-russia-2018/geosearch 0.008s
ããããããªãæãããªæ¬ é¥ã1ã€ãããŸãã æ€çŽ¢ç¯å²ã倧ããã人å£å¯åºŠãäœãå Žåãget-sã§å€æ°ã®ã»ã«ã確èªããå¿
èŠããããŸãã ããã»ã©éãã¯ãããŸããã
S2ã«ããã£ããããstorageLevelã¬ãã«ã®ã»ã«ã§ã«ããŒããããã«äŸé ŒããããšãæãåºããŠãã ããã ãããããããã®ã»ã«ã¯éåžžã«å°ãããããå€ãã®ã»ã«ã倿ããŸãã
S2ã«ãç°ãªãã¬ãã«ã®ã»ã«ã§ããã£ããããã«ããŒããããã«äŸé Œã§ããŸãã æ¬¡ã®ããã«ãªããŸãïŒ
rc := s2.RegionCoverer{MaxLevel: i.storageLevel} cu := rc.Covering(cap)
ã芧ã®ãšãããS2ã¯åã®å
åŽã®å€§ããªã»ã«ã䜿çšãããšããžã®åšãã®å°ããªã»ã«ã䜿çšããŸãã åèšãããšãã»ã«ã¯å°ãããªããŸãã
ããããBããªãŒå
ã®ãã倧ããªã»ã«ãããŠãŒã¶ãŒãèŠã€ããããã«ãGetã¯äœ¿çšã§ããªããªããŸããã å倧ããªã»ã«ã®S2ã«storageLevelã¬ãã«ã®æåã®å
éšã»ã«ã®çªå·ãšæåŸã®çªå·ãåãåãããç¯å²ã¹ãã£ã³ãã€ãŸãã¯ãšãªãfromããšãtoãã®å©ããåããŠæ¢ã«æ€çŽ¢ããå¿
èŠããããŸãã
func (i *Index) SearchFaster(lon, lat float64, radius uint32) ([]uint32, error) {
倿Žã¯éåžžã«å°ããªãã®ã§ããããã®éçšã§èŠãŠã¿ãŸãããã ãµã€ã¯ã«ã§æ€çŽ¢ãè¡ãåçŽãªãã³ãããŒã¯ãäœæããå®è¡ããŸãã
var res []uint32 func BenchmarkSearch(b *testing.B) { indx := prepare() for i := 0; i < bN; i++ { res, _ = indx.Search(14.1313, 14.1313, 50000) } } func BenchmarkSearchFaster(b *testing.B) { indx := prepare() for i := 0; i < bN; i++ { res, _ = indx.SearchFaster(14.1313, 14.1313, 50000) } }
ç§ãã¡ã®å°ããªå€åã¯ã3æ¡ã®å éããããããŸããã æªããªãïŒ
$ go test -bench=. goos: darwin goarch: amd64 pkg: github.com/mkevac/gophercon-russia-2018/geosearch BenchmarkSearch-8 500 3097564 ns/op BenchmarkSearchFaster-8 200000 7198 ns/op PASS ok github.com/mkevac/gophercon-russia-2018/geosearch 3.393s
ãžãªãã§ã³ã·ã³ã°ãµãŒãã¹ãäœæããŸããã
ååŸã«ããæ€çŽ¢ã®è¶
ã·ã³ãã«ãªå®è£
ãæ€èšããŸããã åãã·ã³ãã«ãªãžãªãã§ã³ã·ã³ã°ã®å®è£
ãç°¡åã«èŠãŠã¿ãŸããããã€ãŸããã©ã®ããªãŽã³ã«ãããããŸãã¯ã©ã®ããªãŽã³ã«æãè¿ããã倿ããŸãã
ããã§ãã€ã³ããã¯ã¹ã«ã¯BããªãŒãå¿
èŠã§ãããããã«å ããŠã远å ããããã¹ãŠã®ããªãŽã³ã®ã°ããŒãã«ãããããããŸãã
type Index struct { storageLevel int bt *btree.BTree polygons map[uint32]*s2.Polygon } func NewIndex(storageLevel int) *Index { return &Index{ storageLevel: storageLevel, bt: btree.New(35), polygons: make(map[uint32]*s2.Polygon), } }
åãšåæ§ã«ãBããªãŒã®ããŒãã«ãªã¹ããä¿åããŸãããããã§ã¯storageLevelã¬ãã«ã®ã»ã«ã«ããããªãŽã³ã®ã¿ãä¿åããŸãã
type IndexItem struct { cellID s2.CellID polygonsInCell []uint32 } func (ii IndexItem) Less(than btree.Item) bool { return uint64(ii.cellID) < uint64(than.(IndexItem).cellID) }
ãã®äŸã§ã¯ãã€ã³ããã¯ã¹ã«ããªãŽã³ã远å ããçŸåšã®ããªãŽã³ãèŠã€ããæãè¿ãããªãŽã³ãèŠã€ããæ©èœããããŸãã
func (i *Index) AddPolygon(polygonID uint32, vertices []s2.LatLng) error {} func (i *Index) Search(lon, lat float64) ([]uint32, error) {} func (i *Index) SearchNearest(lon, lat float64) ([]uint32, error) {}
ããªãŽã³ã远å ããããšããå§ããŸãããã ããªãŽã³ã¯é ç¹ã®ãªã¹ãã«ãã£ãŠå®çŸ©ãããS2ã¯é ç¹ã®é åºãåæèšåãã«ãªãããšãæåŸ
ããŠããŸãã ãããããšã©ãŒã®å Žåããæ£èŠåãã®æ©èœããããŸããããã¯ããããè£è¿ãã«ããŠããŸãã
func (i *Index) AddPolygon(polygonID uint32, vertices []s2.LatLng) error { points := func() (res []s2.Point) { for _, vertex := range vertices { point := s2.PointFromLatLng(vertex) res = append(res, point) } return }() loop := s2.LoopFromPoints(points) loop.Normalize() polygon := s2.PolygonFromLoops([]*s2.Loop{loop})
é ç¹ã®ãªã¹ããã«ãŒãã«å€æããŠãããããªãŽã³ã«å€æããŸãã ãããã®éãã¯ãå€è§åœ¢ãè€æ°ã®ã«ãŒãã§æ§æãããããšãã°ããŒããã®ããã«ç©Žãããããšã§ãã
åã®äŸã®ããã«ãS2ã«ããªãŽã³ãã»ã«ã§ã«ããŒããè¿ãããåã»ã«ãBããªãŒã«è¿œå ããããã«äŸé ŒããŸãã ããŠãã°ããŒãã«ãããã§ã
func (i *Index) AddPolygon(polygonID uint32, vertices []s2.LatLng) error {
ãã®äŸã§ã¯ãããªãŽã³æ€çŽ¢æ©èœã¯ç°¡åã§ãã åãšåãããã«ãstorageLevelã¬ãã«ã§CellIDã®æ€çŽ¢åº§æšã倿ããBããªãŒã§ãã®ã»ã«ãæ€çŽ¢ãããã©ãããæ±ºå®ããŸãã
func (i *Index) Search(lon, lat float64) ([]uint32, error) { latlng := s2.LatLngFromDegrees(lat, lon) cellID := s2.CellIDFromLatLng(latlng) cellIDOnStorageLevel := cellID.Parent(i.storageLevel) item := i.bt.Get(IndexItem{cellID: cellIDOnStorageLevel}) if item != nil { return item.(IndexItem).polygonsInCell, nil } return []uint32{}, nil }
æãè¿ãããªãŽã³ãèŠã€ããã®ã¯ããå°ãè€éã§ãã ãŸããçªç¶äœããã®ãã¬ãŒãã³ã°ã®å Žã«ããã®ãã©ããã倿ããããšããŸãã ããã§ãªãå Žåã¯ãååŸãå¢ãããŠæ€çŽ¢ããŸãïŒä»¥äžã§ãã©ã®ããã«èŠãããã瀺ããŸãïŒã ããŠãæãè¿ãããªãŽã³ãèŠã€ãã£ããããããããã£ã«ã¿ãªã³ã°ããŠæãè¿ãããªãŽã³ãèŠã€ããèŠã€ãã£ãåããªãŽã³ãŸã§ã®è·é¢ãèšç®ããŠãæçè·é¢ã®ããªãŽã³ãååŸããŸãã
func (i *Index) SearchNearest(lon, lat float64) ([]uint32, error) {
ãååŸã®å¢å ããšã¯ã©ãããæå³ã§ããïŒ æåã®åçã§ã¯ãæ€çŽ¢ã®äžå¿ã§ãããªã¬ã³ãžè²ã®ã»ã«ã衚瀺ãããŸãã æåã«ã飿¥ãããã¹ãŠã®ã»ã«ã§æãè¿ãããªãŽã³ãèŠã€ããããšããŸãïŒå³ã§ã¯ç°è²ã§ãïŒã ããã§äœãèŠã€ãããªãå Žåã¯ã2çªç®ã®å³ã®ããã«å¥ã®ã¬ãã«ã«ç§»åããŸãã ãªã©ãªã©ã
ãNeighborsãã¯ãAllNeighbors颿°ã«ãã£ãŠæäŸãããŸãã ååŸããã»ã«ããã£ã«ã¿ãªã³ã°ããŠããã§ã«è¡šç€ºããã»ã«ãåé€ããå¿
èŠããªãéãã
func (i *Index) searchNextLevel(radiusEdges []s2.CellID, alreadyVisited *[]s2.CellID) (found []IndexItem, searched []s2.CellID) { for _, radiusEdge := range radiusEdges { neighbors := radiusEdge.AllNeighbors(i.storageLevel) for _, neighbor := range neighbors { if in(*alreadyVisited, neighbor) { continue } *alreadyVisited = append(*alreadyVisited, neighbor) searched = append(searched, neighbor) item := i.bt.Get(IndexItem{cellID: neighbor}) if item != nil { found = append(found, item.(IndexItem)) } } } return }
å®éãããããã¹ãŠã§ãã ãã®äŸã§ã¯ããã¹ããäœæããŸããããæåããŸãã
ãããŸãã¯å®å
šãªäŸãèŠãããšã«èå³ããããªããã¹ã©ã€ããšäžç·ã«
ããã§ããããèŠã€ããããšãã§ããŸãã
ãããã«
座æšãŸãã¯ããã€ãã®ããè€éãªå°ççæ©èœã§åäœããæ€çŽ¢ãµãŒãã¹ãèšè¿°ããå¿
èŠããããPostGISã®ãããªæ¢è£œã®DBMSã䜿çšããããªãããŸãã¯äœ¿çšã§ããªãå Žåã¯ãS2ãæ€èšããŠãã ããã
ããã¯ãåºæ¬çãªãã®ãšåº§æšãšå°çãæäœããããã®ãã¬ãŒã ã¯ãŒã¯ãæäŸããã¯ãŒã«ã§ã·ã³ãã«ãªããŒã«ã§ãã Badooã®ç§ãã¡ã¯S2ãæ¬åœã«æ°ã«å
¥ã£ãŠãããéåžžã«æšå¥šããŠããŸãã
ãããããé¡ãããŸãïŒ
UpdïŒãããŠä»ããããªãå°çããŸããïŒ