penguin/golifehk

@golifehk bot in Telegram

commit 33a7c04e0980d21dd27610f97e372503388b8c43

author斟酌 鵬兄 <tgckpg@gmail.com>
date2026-03-08T07:38:28Z
subjectCan now accept location
commit 33a7c04e0980d21dd27610f97e372503388b8c43
Author: 斟酌 鵬兄 <tgckpg@gmail.com>
Date:   2026-03-08T07:38:28Z

    Can now accept location
---
 adaptors/tg/botsend.go              | 33 +++++++++++-----
 datasources/cjlookup/QueryResult.go |  6 +--
 datasources/kmb/BusStop.go          | 62 +++++++++++++++++------------
 datasources/kmb/QueryResult.go      | 78 +++++++++++++++++++++++++++----------
 datasources/kmb/RouteStop.go        |  7 +---
 datasources/kmb/query.go            | 26 +++++++++++--
 datasources/kmb/routestops.go       | 66 +++++++++++++++++--------------
 datasources/mtr/bus/QueryResult.go  | 49 +++++++++++++++--------
 main.go                             | 70 +++++++++++++++++++++------------
 query/GeoLocation.go                | 13 ++++++-
 query/match_locations.go            |  9 +++--
 query/objects.go                    |  7 +++-
 12 files changed, 282 insertions(+), 144 deletions(-)

diff --git a/adaptors/tg/botsend.go b/adaptors/tg/botsend.go
index 9885d8f..8229da2 100644
--- a/adaptors/tg/botsend.go
+++ b/adaptors/tg/botsend.go
@@ -1,6 +1,7 @@
 package tg
 
 import (
+	"fmt"
 	tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
 	query "github.com/tgckpg/golifehk/query"
 )
@@ -32,25 +33,37 @@ func BotSend(bot *tgbotapi.BotAPI, update *tgbotapi.Update, qResult query.IQuery
 		return false, nil
 	}
 
-	msg = tgbotapi.NewMessage(update.Message.Chat.ID, mesg)
+	var chatId int64
+	if update.Message != nil {
+		chatId = update.Message.Chat.ID
+		msg.ReplyToMessageID = update.Message.MessageID
+	}
+
+	if update.CallbackQuery != nil {
+		chatId = update.CallbackQuery.Message.Chat.ID
+	}
+
+	msg = tgbotapi.NewMessage(chatId, mesg)
 	msg.ParseMode = "MarkdownV2"
 
 	switch mesgType {
 	case "PlainText":
 	case "Table":
 
-		button := tgbotapi.NewInlineKeyboardButtonData(
-			"Show Status", // what user sees
-			"status_cmd",  // what bot receives
-		)
-
-		msg.ReplyMarkup = tgbotapi.NewInlineKeyboardMarkup(
-			tgbotapi.NewInlineKeyboardRow(button, button),
-		)
+		buttonRows := [][]tgbotapi.InlineKeyboardButton{}
+		for _, row := range qResult.GetTableData() {
+			buttons := []tgbotapi.InlineKeyboardButton{}
+			for _, cell := range row {
+				button := tgbotapi.NewInlineKeyboardButtonData(cell.Name, cell.Value)
+				buttons = append(buttons, button)
+				fmt.Println(cell)
+			}
+			buttonRows = append(buttonRows, buttons)
+		}
 
+		msg.ReplyMarkup = tgbotapi.NewInlineKeyboardMarkup(buttonRows...)
 	}
 
-	msg.ReplyToMessageID = update.Message.MessageID
 	bot.Send(msg)
 	return true, nil
 }
diff --git a/datasources/cjlookup/QueryResult.go b/datasources/cjlookup/QueryResult.go
index 16a9272..976583c 100644
--- a/datasources/cjlookup/QueryResult.go
+++ b/datasources/cjlookup/QueryResult.go
@@ -40,9 +40,9 @@ func writeCCharInfo(sb *strings.Builder, cc *CChar) {
 	}
 }
 
-func (this QueryResult) DataType() string                    { return this.ResultType }
-func (this QueryResult) Consumed() bool                      { return this.isConsumed }
-func (this QueryResult) GetTableData() [][]map[string]string { return nil }
+func (this QueryResult) DataType() string                  { return this.ResultType }
+func (this QueryResult) Consumed() bool                    { return this.isConsumed }
+func (this QueryResult) GetTableData() [][]query.TableCell { return nil }
 
 func (this QueryResult) Message() (string, error) {
 
diff --git a/datasources/kmb/BusStop.go b/datasources/kmb/BusStop.go
index 0019705..79b7ffe 100644
--- a/datasources/kmb/BusStop.go
+++ b/datasources/kmb/BusStop.go
@@ -1,40 +1,52 @@
 package kmb
 
 import (
-    i18n "github.com/tgckpg/golifehk/i18n"
+	i18n "github.com/tgckpg/golifehk/i18n"
+	query "github.com/tgckpg/golifehk/query"
 )
 
+type BusStopJson struct {
+	BusStopId string  `json:"stop"`
+	Latitude  float64 `json:"lat,string"`
+	Longitude float64 `json:"long,string"`
+	Name_en   string  `json:"name_en"`
+	Name_tc   string  `json:"name_tc"`
+	Name_sc   string  `json:"name_sc"`
+}
+
 type BusStop struct {
-    BusStopId string `json:"stop"`
-    Latitude float64 `json:"lat,string"`
-    Longtitude float64 `json:"long,string"`
-    Name_en string `json:"name_en"`
-    Name_tc string `json:"name_tc"`
-    Name_sc string `json:"name_sc"`
+	BusStopId string
 
-    // Routes[ Route ][ Direction ]
-    Routes *[] *RouteStop
+	// Routes[ Route ][ Direction ]
+	Routes *[]*RouteStop
+
+	i18n.Generics
+	query.GeoLocation
+}
 
-    i18n.Generics
+type BusStopsJson struct {
+	Type        string         `json:"type"`
+	Version     string         `json:"version"`
+	DateCreated string         `json:"generated_timestamp"`
+	BusStops    []*BusStopJson `json:"data"`
 }
 
-type BusStops struct {
-    Type string `json:"type"`
-    Version string `json:"version"`
-    DateCreated string `json:"generated_timestamp"`
-    BusStops [] *BusStop `json:"data"`
+func (b BusStop) Register(registers map[string]struct{}) bool {
+	if _, ok := registers[b.BusStopId]; ok {
+		return false
+	}
+	registers[b.BusStopId] = struct{}{}
+	return true
 }
 
-func ( this *BusStop ) Reload() {
-    i18n_Name := map[string] string{}
-    i18n_Name["en"] = this.Name_en
-    i18n_Name["zh-Hant"] = this.Name_tc
+func (this *BusStop) Reload() {
+	searchData := []*string{}
+	searchData = append(searchData, &this.BusStopId)
 
-    searchData := [] *string{}
-    searchData = append( searchData, &this.Name_en )
-    searchData = append( searchData, &this.Name_tc )
+	for _, v := range *this.Name {
+		searchData = append(searchData, &v)
+	}
 
-    this.Name = &i18n_Name
-    this.Key = &this.BusStopId
-    this.SearchData = &searchData
+	this.Key = &this.BusStopId
+	this.SearchData = &searchData
 }
diff --git a/datasources/kmb/QueryResult.go b/datasources/kmb/QueryResult.go
index a1bdce0..730c411 100644
--- a/datasources/kmb/QueryResult.go
+++ b/datasources/kmb/QueryResult.go
@@ -17,6 +17,8 @@ type QueryResult struct {
 	Query     *query.QueryObject
 
 	isConsumed bool
+	dataType   string
+	tableData  [][]query.TableCell
 }
 
 func writeRouteHead(sb *strings.Builder, r *RouteStop) {
@@ -50,12 +52,14 @@ func writeShortRoute(lang *string, sb *strings.Builder, r *RouteStop) {
 	sb.WriteString("\n")
 }
 
-func (this QueryResult) DataType() string                    { return "MarkdownV2" }
-func (this QueryResult) Consumed() bool                      { return this.isConsumed }
-func (this QueryResult) GetTableData() [][]map[string]string { return nil }
+func (this QueryResult) DataType() string                  { return this.dataType }
+func (this QueryResult) Consumed() bool                    { return this.isConsumed }
+func (this QueryResult) GetTableData() [][]query.TableCell { return this.tableData }
 
 func (this *QueryResult) Message() (string, error) {
 
+	this.dataType = "PlainText"
+
 	if this.Error != nil {
 		return "", this.Error
 	}
@@ -66,27 +70,61 @@ func (this *QueryResult) Message() (string, error) {
 
 		// Print Stop Names, then print the list of routes
 		if this.Query.Key == "" {
-			busStops := map[string]*BusStop{}
-			for _, item := range *this.Query.Results {
-				var r *RouteStop
-				r = any(item).(*RouteStop)
-
-				b := r.BusStop
-				if b.Routes == nil {
-					continue
+			loc := this.Query.Message.Location
+			if loc != nil {
+				sb.WriteString("九巴 100m")
+				this.dataType = "Table"
+
+				table := [][]query.TableCell{}
+
+				for _, item := range *this.Query.Results {
+					b := any(item).(*BusStop)
+
+					row := []query.TableCell{}
+
+					cell := query.TableCell{
+						Name:  fmt.Sprintf("%.2fm %s", b.Dist(loc.Lat(), loc.Lon()), (*b.Name)[this.Lang]),
+						Value: fmt.Sprintf("%s", b.BusStopId),
+					}
+					row = append(row, cell)
+
+					for _, r := range *b.Routes {
+						sb_i := strings.Builder{}
+						writeRouteHead(&sb_i, r)
+						cell := query.TableCell{
+							Name:  sb_i.String(),
+							Value: fmt.Sprintf("%s %s", r.RouteId, (*b.Name)[this.Lang]),
+						}
+						row = append(row, cell)
+					}
+
+					table = append(table, row)
 				}
+				this.tableData = table
 
-				busStops[b.BusStopId] = b
-			}
+			} else {
+				busStops := map[string]*BusStop{}
+				for _, item := range *this.Query.Results {
+					var r *RouteStop
+					r = any(item).(*RouteStop)
+
+					b := r.BusStop
+					if b.Routes == nil {
+						continue
+					}
+
+					busStops[b.BusStopId] = b
+				}
 
-			for _, b := range busStops {
-				utils.WriteMDv2Text(&sb, (*b.Name)[this.Lang])
-				sb.WriteString("\n  ")
-				for _, route := range *b.Routes {
-					writeRouteHead(&sb, route)
-					sb.WriteString(" ")
+				for _, b := range busStops {
+					utils.WriteMDv2Text(&sb, (*b.Name)[this.Lang])
+					sb.WriteString("\n  ")
+					for _, route := range *b.Routes {
+						writeRouteHead(&sb, route)
+						sb.WriteString(" ")
+					}
+					sb.WriteString("\n")
 				}
-				sb.WriteString("\n")
 			}
 
 			// We got a route key
diff --git a/datasources/kmb/RouteStop.go b/datasources/kmb/RouteStop.go
index d9554a4..6e2ec8c 100644
--- a/datasources/kmb/RouteStop.go
+++ b/datasources/kmb/RouteStop.go
@@ -40,13 +40,8 @@ func (routeStop RouteStop) NextStop() *RouteStop {
 
 func (this *RouteStop) Reload() {
 
-	searchData := []*string{}
-	busStop := *this.BusStop
-	searchData = append(searchData, &busStop.Name_en)
-	searchData = append(searchData, &busStop.Name_tc)
-
 	this.Key = &this.RouteId
-	this.SearchData = &searchData
+	this.SearchData = this.BusStop.SearchData
 }
 
 type ByRoute []*RouteStop
diff --git a/datasources/kmb/query.go b/datasources/kmb/query.go
index c6b18d2..ba04adc 100644
--- a/datasources/kmb/query.go
+++ b/datasources/kmb/query.go
@@ -9,19 +9,39 @@ import (
 func Query(q query.QueryMessage) query.IQueryResult {
 
 	lang := q.Lang
-	message := q.Text
 
 	var qo *query.QueryObject
 	var err error
+	var routeStops *[]query.ISearchable
 
 	qr := QueryResult{Lang: lang}
-	routeStops, err := getRouteStops()
+
+	busStops, err := readBusStopsData()
+	if err != nil {
+		qr.Error = err
+		goto qrReturn
+	}
+
+	routeStops, err = getRouteStops(busStops)
 	if err != nil {
 		qr.Error = err
 		goto qrReturn
 	}
 
-	qo, err = query.MatchKeys(strings.ToUpper(message), routeStops)
+	if q.Text != "" {
+		qo, err = query.MatchKeys(strings.ToUpper(q.Text), routeStops)
+	} else if q.Location != nil {
+		bList := []query.ISearchable{}
+
+		for _, b := range *busStops {
+			bList = append(bList, b)
+		}
+
+		qo, err = query.MatchNearest(*q.Location, &bList, 100, 3)
+	}
+
+	qo.Message = &q
+
 	if err != nil {
 		qr.Error = err
 		goto qrReturn
diff --git a/datasources/kmb/routestops.go b/datasources/kmb/routestops.go
index 2bfb63a..1e0dcc0 100644
--- a/datasources/kmb/routestops.go
+++ b/datasources/kmb/routestops.go
@@ -32,7 +32,6 @@ func readRouteStopsData(busStops *map[string]*BusStop, buff *bytes.Buffer) (*[]*
 		if busStop == nil {
 			busStop = &BusStop{
 				BusStopId: entry.StationId,
-				Name_en:   "???", Name_tc: "???", Name_sc: "???",
 			}
 			busStop.Reload()
 
@@ -76,37 +75,15 @@ func readRouteStopsData(busStops *map[string]*BusStop, buff *bytes.Buffer) (*[]*
 	return &allRouteStops, nil
 }
 
-func readBusStopsData(buff *bytes.Buffer) (*map[string]*BusStop, error) {
-	busStopsData := BusStops{}
-	err := json.Unmarshal(buff.Bytes(), &busStopsData)
-	if err != nil {
-		return nil, err
-	}
-
-	busStopMap := map[string]*BusStop{}
-	for _, entry := range busStopsData.BusStops {
-
-		entry.Reload()
-
-		if _, ok := busStopMap[entry.BusStopId]; ok {
-			return nil, fmt.Errorf("Duplicated BusStop: %s", entry.BusStopId)
-		}
-
-		busStopMap[entry.BusStopId] = entry
-	}
-	return &busStopMap, nil
-}
-
-func getRouteStops() (*[]query.ISearchable, error) {
-	var busStopMap *map[string]*BusStop
+func readBusStopsData() (*map[string]*BusStop, error) {
+	busStopsData := BusStopsJson{}
 
 	QUERY_FUNC := func() (io.ReadCloser, error) {
 		return utils.HttpGet("https://data.etabus.gov.hk/v1/transport/kmb/stop")
 	}
 
 	PARSE_FUNC := func(buff *bytes.Buffer) error {
-		var err error
-		busStopMap, err = readBusStopsData(buff)
+		err := json.Unmarshal(buff.Bytes(), &busStopsData)
 		return err
 	}
 
@@ -120,18 +97,49 @@ func getRouteStops() (*[]query.ISearchable, error) {
 		return nil, err
 	}
 
+	busStopMap := map[string]*BusStop{}
+	for _, entry := range busStopsData.BusStops {
+
+		b := BusStop{
+			BusStopId: entry.BusStopId,
+		}
+
+		b.Latitude = entry.Latitude
+		b.Longitude = entry.Longitude
+
+		n := map[string]string{
+			"en":      entry.Name_en,
+			"zh-Hant": entry.Name_tc,
+			"zh-Hans": entry.Name_sc,
+		}
+
+		b.Name = &n
+
+		b.Reload()
+
+		if _, ok := busStopMap[b.BusStopId]; ok {
+			return nil, fmt.Errorf("Duplicated BusStop: %s", b.BusStopId)
+		}
+
+		busStopMap[b.BusStopId] = &b
+	}
+	return &busStopMap, nil
+}
+
+func getRouteStops(busStopMap *map[string]*BusStop) (*[]query.ISearchable, error) {
 	var routeStops *[]*RouteStop
-	QUERY_FUNC = func() (io.ReadCloser, error) {
+
+	QUERY_FUNC := func() (io.ReadCloser, error) {
 		return utils.HttpGet("https://data.etabus.gov.hk/v1/transport/kmb/route-stop")
 	}
 
-	PARSE_FUNC = func(buff *bytes.Buffer) error {
+	PARSE_FUNC := func(buff *bytes.Buffer) error {
 		var err error
 		routeStops, err = readRouteStopsData(busStopMap, buff)
 		return err
 	}
 
-	cs, err = utils.CacheStreamEx(JSON_ROUTESTOPS, QUERY_FUNC)
+	cs, err := utils.CacheStreamEx(JSON_ROUTESTOPS, QUERY_FUNC)
 	if err != nil {
 		return nil, err
 	}
diff --git a/datasources/mtr/bus/QueryResult.go b/datasources/mtr/bus/QueryResult.go
index 0434b9d..87556aa 100644
--- a/datasources/mtr/bus/QueryResult.go
+++ b/datasources/mtr/bus/QueryResult.go
@@ -18,6 +18,8 @@ type QueryResult struct {
 	Query     *query.QueryObject
 
 	isConsumed bool
+	dataType   string
+	tableData  [][]query.TableCell
 }
 
 func writeShortRoute(lang *string, sb *strings.Builder, b *BusStop) {
@@ -38,11 +40,13 @@ func writeShortRoute(lang *string, sb *strings.Builder, b *BusStop) {
 	sb.WriteString("\n")
 }
 
-func (this QueryResult) DataType() string                    { return "MarkdownV2" }
-func (this QueryResult) Consumed() bool                      { return this.isConsumed }
-func (this QueryResult) GetTableData() [][]map[string]string { return nil }
+func (this QueryResult) DataType() string                  { return this.dataType }
+func (this QueryResult) Consumed() bool                    { return this.isConsumed }
+func (this QueryResult) GetTableData() [][]query.TableCell { return this.tableData }
 
-func (this QueryResult) Message() (string, error) {
+func (this *QueryResult) Message() (string, error) {
+
+	this.dataType = "PlainText"
 
 	if this.Error != nil {
 		return "", this.Error
@@ -67,29 +71,40 @@ func (this QueryResult) Message() (string, error) {
 			loc := q.Message.Location
 			if loc != nil {
 
-				// Print nearest bus stops
+				this.dataType = "Table"
+				sb.WriteString("K巴 100m")
+
+				table := [][]query.TableCell{}
+
 				for _, entry := range *q.Results {
 					busStop := any(entry).(*BusStop)
 
-					utils.WriteMDv2Text(&sb, fmt.Sprintf("%.2fm", busStop.Dist(loc.Lat(), loc.Lon())))
-					sb.WriteString(" ")
-					sb.WriteString(" [")
-					utils.WriteMDv2Text(&sb, busStop.RouteId)
+					sb_i := strings.Builder{}
+					sb_i.WriteString(fmt.Sprintf("%.2fm", busStop.Dist(loc.Lat(), loc.Lon())))
+					sb_i.WriteString(" ")
+					utils.WriteMDv2Text(&sb_i, busStop.RouteId)
 					d := busStop.Direction
 					if d == "O" {
-						sb.WriteString("↑")
+						sb_i.WriteString("↑")
 					} else if d == "I" {
-						sb.WriteString("↓")
+						sb_i.WriteString("↓")
 					} else {
-						sb.WriteString("\\?")
+						sb_i.WriteString("\\?")
 					}
-					utils.WriteMDv2Text(&sb, (*busStop.Name)[this.Lang])
-					utils.WriteMDv2Text(&sb, busStop.RouteId)
-					utils.WriteMDv2Text(&sb, (*busStop.Name)[this.Lang])
-					sb.WriteString(")")
-					sb.WriteString("\n")
+					sb_i.WriteString(" ")
+					utils.WriteMDv2Text(&sb_i, (*busStop.Name)[this.Lang])
+
+					row := []query.TableCell{
+						query.TableCell{
+							Name:  sb_i.String(),
+							Value: fmt.Sprintf("%s %s", busStop.RouteId, (*busStop.Name)[this.Lang]),
+						},
+					}
+
+					table = append(table, row)
 				}
 
+				this.tableData = table
 			} else {
 				sort.Sort(query.ByKey(*q.Results))
 				for _, entry := range *q.Results {
diff --git a/main.go b/main.go
index 2bf2ba7..f32b85f 100644
--- a/main.go
+++ b/main.go
@@ -30,6 +30,22 @@ func main() {
 	updates := bot.GetUpdatesChan(u)
 
 	for update := range updates {
+
+		if update.CallbackQuery != nil {
+			callback := tgbotapi.NewCallback(update.CallbackQuery.ID, "")
+			bot.Request(callback)
+
+			q := query.QueryMessage{Lang: "zh-Hant", Text: update.CallbackQuery.Data}
+			f_sent, f_err := processQuery(bot, &update, q)
+
+			if !f_sent && f_err != nil {
+				mesg := utils.MDv2Text(fmt.Sprintf("%s", f_err))
+				tgadaptor.BotSendText(bot, &update, &mesg)
+			}
+
+			continue
+		}
+
 		if update.Message == nil {
 			continue
 		}
@@ -45,15 +61,6 @@ func main() {
 			continue
 		}
 
-		f_queries := []func(query.QueryMessage) query.IQueryResult{
-			cjlookup.Query,
-			mtrbus.Query,
-			kmb.Query,
-		}
-
-		var f_sent bool = false
-		var f_err error = nil
-
 		tgMesg := update.Message
 		q := query.QueryMessage{Lang: "zh-Hant", Text: tgMesg.Text}
 
@@ -61,27 +68,42 @@ func main() {
 			q.Location = &query.GeoLocation{tgMesg.Location.Latitude, tgMesg.Location.Longitude}
 		}
 
-		for _, Query := range f_queries {
+		f_sent, f_err := processQuery(bot, &update, q)
+
+		if !isGroup && !f_sent && f_err != nil {
+			mesg := utils.MDv2Text(fmt.Sprintf("%s", f_err))
+			tgadaptor.BotSendText(bot, &update, &mesg)
+		}
+	}
+}
+
+func processQuery(bot *tgbotapi.BotAPI, update *tgbotapi.Update, q query.QueryMessage) (bool, error) {
+	var f_sent bool = false
+	var f_err error = nil
 
-			qResult := Query(q)
-			sent, err := tgadaptor.BotSend(bot, &update, qResult)
+	f_queries := []func(query.QueryMessage) query.IQueryResult{
+		cjlookup.Query,
+		mtrbus.Query,
+		kmb.Query,
+	}
 
-			if sent {
-				f_sent = true
-			}
+	for _, Query := range f_queries {
 
-			if err != nil {
-				f_err = err
-			}
+		qResult := Query(q)
+		sent, err := tgadaptor.BotSend(bot, update, qResult)
 
-			if qResult.Consumed() {
-				break
-			}
+		if sent {
+			f_sent = true
 		}
 
-		if !isGroup && !f_sent && f_err != nil {
-			mesg := utils.MDv2Text(fmt.Sprintf("%s", f_err))
-			tgadaptor.BotSendText(bot, &update, &mesg)
+		if err != nil {
+			f_err = err
+		}
+
+		if qResult.Consumed() {
+			break
 		}
 	}
+
+	return f_sent, f_err
 }
diff --git a/query/GeoLocation.go b/query/GeoLocation.go
index db8f654..30c5800 100644
--- a/query/GeoLocation.go
+++ b/query/GeoLocation.go
@@ -5,6 +5,14 @@ import (
 	"sort"
 )
 
+type IGeoLocation interface {
+	HasGeoLocation() bool
+	Lat() float64
+	Lon() float64
+	Dist(lat, lon float64) float64
+	Register(map[string]struct{}) bool
+}
+
 type GeoLocation struct {
 	Latitude  float64
 	Longitude float64
@@ -19,7 +27,7 @@ func (b GeoLocation) Dist(lat float64, lon float64) float64 {
 	var dist float64
 	geodesic.WGS84.Inverse(
 		lat, lon,
-		b.Latitude, b.Longitude,
+		b.Lat(), b.Lon(),
 		&dist, nil, nil,
 	)
 	return dist
@@ -37,13 +45,14 @@ func (b NoGeoLocation) Register(map[string]struct{}) bool {
 
 type GeoLocations []ISearchable
 
-func (m GeoLocations) SortByNearest(p GeoLocation) {
+func (m GeoLocations) SortByNearest(p IGeoLocation) {
 	sort.Slice(m, func(i, j int) bool {
 		return m[i].Dist(p.Lat(), p.Lon()) < m[j].Dist(p.Lat(), p.Lon())
 	})
 }
 
 func (b GeoLocation) Register(map[string]struct{}) bool {
+	panic("GeoLocation: Default is called")
 	return false
 }
 
diff --git a/query/match_locations.go b/query/match_locations.go
index 7fe39f0..96f07ad 100644
--- a/query/match_locations.go
+++ b/query/match_locations.go
@@ -4,7 +4,7 @@ import (
 	"fmt"
 )
 
-func MatchNearest(p GeoLocation, entries *[]ISearchable, dist float64, limit int) (*QueryObject, error) {
+func MatchNearest(p IGeoLocation, entries *[]ISearchable, dist float64, limit int) (*QueryObject, error) {
 
 	terms := []*QTerm{
 		{
@@ -19,9 +19,10 @@ func MatchNearest(p GeoLocation, entries *[]ISearchable, dist float64, limit int
 	locs.SortByNearest(p)
 
 	matches := []ISearchable{}
-	for i, loc := range *locs {
-		if i < limit {
-			matches = append(matches, loc)
+	for i, item := range *locs {
+		loc := item.(IGeoLocation)
+		if i < limit && loc.Dist(p.Lat(), p.Lon()) <= dist {
+			matches = append(matches, item)
 		}
 	}
 
diff --git a/query/objects.go b/query/objects.go
index bcbe66a..50120e0 100644
--- a/query/objects.go
+++ b/query/objects.go
@@ -19,9 +19,14 @@ type QueryObject struct {
 	Results     *[]ISearchable
 }
 
+type TableCell struct {
+	Name  string
+	Value string
+}
+
 type IQueryResult interface {
 	Message() (string, error)
 	DataType() string
-	GetTableData() [][]map[string]string
+	GetTableData() [][]TableCell
 	Consumed() bool
 }