Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue with WITHIN Query in Tile38 Geofencing #746

Open
hitchanatanael opened this issue Jul 13, 2024 · 10 comments
Open

Issue with WITHIN Query in Tile38 Geofencing #746

hitchanatanael opened this issue Jul 13, 2024 · 10 comments

Comments

@hitchanatanael
Copy link

hitchanatanael commented Jul 13, 2024

I am experiencing an issue with the WITHIN query in Tile38 for geofencing. I have successfully set up the geofence using the SET command, but when I execute the WITHIN query, it always returns that the user is outside the geofence, even when the coordinates should be inside the defined area.

Here are the details of my setup:

  • **Laravel Version: 10
  • **Tile38 Version: 1.33.0
  • **Predis Version: v2.2.2
  • **PHP Version: 8.2.12

Code:

namespace App\Http\Controllers;

use App\Models\Absensi;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Predis\Client;

class HomeController extends Controller
{
    protected $tile38;

    public function __construct()
    {
        // Inisialisasi koneksi ke Tile38
        try {
            $this->tile38 = new Client([
                'scheme' => 'tcp',
                'host'   => config('database.redis.tile38.host'),
                'port'   => config('database.redis.tile38.port'),
            ]);
            Log::info('Successfully connected to Tile38');
        } catch (\Exception $e) {
            Log::error('Failed to connect to Tile38: ' . $e->getMessage());
        }
    }

    private function checkGeofence($latitude, $longitude)
    {
        try {
            $pingResponse = $this->tile38->ping();
            if ($pingResponse->getPayload() !== 'PONG') {
                Log::error('Tidak dapat terhubung ke Tile38.');
                return ['status' => false, 'message' => 'Tidak dapat terhubung ke Tile38.'];
            }

            $geoJson = [
                'type' => 'Polygon',
                'coordinates' => [
                    [
                        [101.37970257356153, 0.478055156390471],
                        [101.37975609768263, 0.4780623891280328],
                        [101.37977851994957, 0.47807034513933927],
                        [101.37982408778241, 0.47802839526145813],
                        [101.37982408778241, 0.478007420322403],
                        [101.37982625767923, 0.4779604075277617],
                        [101.37978141314531, 0.4779437722311241],
                        [101.37974597149756, 0.4779524515163407],
                        [101.37970691335514, 0.4779871686570672],
                        [101.37970257356153, 0.478055156390471]
                    ]
                ]
            ];

            $geoJsonString = json_encode($geoJson);

            $setCommand = ['SET', 'geofence', 'mygeofence', 'OBJECT', $geoJsonString];
            $setResponse = $this->tile38->executeRaw($setCommand);
            Log::info('Tile38 set response', ['response' => $setResponse]);

            $keysCommand = ['KEYS', '*'];
            $keysResponse = $this->tile38->executeRaw($keysCommand);
            Log::info('Tile38 keys response', ['response' => $keysCommand]);

            $withinCommand = ['WITHIN', 'geofence', 'POINT', $longitude, $latitude];
            $response = $this->tile38->executeRaw($withinCommand);
            Log::info('Tile38 response', ['response' => $response]);

            if (isset($response[0]) && $response[0] === 'OK' && !empty($response[1])) {
                return ['status' => 'success', 'isWithin' => true];
            }

            return ['status' => 'error', 'message' => 'You are outside the geofence area'];
        } catch (\Exception $e) {
            Log::error('Error in Geofence check: ' . $e->getMessage());
            return ['status' => 'error', 'message' => 'Internal Server Error'];
        }
    }

    public function clockIn(Request $request)
    {
        $user = Auth::user();
        $currentTime = now()->toTimeString();
        $currentDate = now()->toDateString();
        $latitude = $request->input('latitude');
        $longitude = $request->input('longitude');

        $geofenceCheck = $this->checkGeofence($latitude, $longitude);

        if ($geofenceCheck['status'] === 'success' && $geofenceCheck['isWithin']) {
            try {
                Absensi::create([
                    'id_user' => $user->id,
                    'tgl_absen' => $currentDate,
                    'jam_masuk' => $currentTime,
                    'koor_masuk' => json_encode(['latitude' => $latitude, 'longitude' => $longitude]),
                    'status' => 1,
                ]);

                Log::info('Absensi berhasil', ['user_id' => $user->id, 'date' => $currentDate, 'time' => $currentTime]);
                return back()->with('success', 'Absensi berhasil');
            } catch (\Exception $e) {
                Log::error('Absensi gagal', ['error' => $e->getMessage()]);
                return back()->with('error', 'Absensi gagal, Silahkan coba lagi');
            }
        }

        Log::info('User is outside the geofence.');
        return back()->with('error', $geofenceCheck['message']);
    }

    public function clockOut(Request $request)
    {
        $user = Auth::user();
        $currentTime = now()->toTimeString();
        $latitude = $request->input('latitude');
        $longitude = $request->input('longitude');

        $geofenceCheck = $this->checkGeofence($latitude, $longitude);

        if ($geofenceCheck['status'] === 'success' && $geofenceCheck['isWithin']) {
            try {
                $absensi = Absensi::where('id_user', $user->id)
                    ->whereDate('tgl_absen', now()->toDateString())
                    ->first();

                if ($absensi && $absensi->status == 1) {
                    $absensi->update([
                        'jam_keluar' => $currentTime,
                        'koor_keluar' => json_encode(['latitude' => $latitude, 'longitude' => $longitude]),
                        'status' => 0
                    ]);

                    return back()->with('success', 'Clock Out berhasil');
                } else {
                    return back()->with('error', 'Clock In belum dilakukan');
                }
            } catch (\Exception $e) {
                Log::error('Clock out gagal', ['error' => $e->getMessage()]);
                return back()->with('error', 'Clock out gagal! Silahkan coba lagi');
            }
        }

        Log::info('User is outside the geofence.');
        return back()->with('error', $geofenceCheck['message']);
    }
}


LOGS:
[2024-07-13 01:59:55] local.INFO: Successfully connected to Tile38  
[2024-07-13 01:59:55] local.INFO: Tile38 set response {"response":"OK"} 
[2024-07-13 01:59:55] local.INFO: Tile38 keys response {"response":["KEYS","*"]} 
[2024-07-13 01:59:55] local.INFO: Tile38 response {"response":[0,[]]} 
[2024-07-13 01:59:55] local.INFO: User is outside the geofence.  
[2024-07-13 01:59:55] local.INFO: Successfully connected to Tile38  
@tidwall
Copy link
Owner

tidwall commented Jul 13, 2024

Perhaps the problem is that the WITHIN POINT command requires that the coordinates are lat/lon. You have lon/lat.

@tidwall
Copy link
Owner

tidwall commented Jul 13, 2024

The geojson appears to be in the correct order to me.

$geoJson = [
    'type' => 'Polygon',
    'coordinates' => [
    	[
            [101.37970257356153, 0.478055156390471],
            [101.37975609768263, 0.4780623891280328],
            [101.37977851994957, 0.47807034513933927],
            [101.37982408778241, 0.47802839526145813],
            [101.37982408778241, 0.478007420322403],
            [101.37982625767923, 0.4779604075277617],
            [101.37978141314531, 0.4779437722311241],
            [101.37974597149756, 0.4779524515163407],
            [101.37970691335514, 0.4779871686570672],
            [101.37970257356153, 0.478055156390471]
        ]
    ]
];

I only issue I see is with the WITHIN command.

$withinCommand = ['WITHIN', 'geofence', 'POINT', $longitude, $latitude];

It should be:

$withinCommand = ['WITHIN', 'geofence', 'POINT', $latitude, $longitude];

@iwpnd
Copy link
Contributor

iwpnd commented Jul 13, 2024

No idea what I was thinking. Shouldn’t answer GitHub issues first thing in the morning. 🙃🙂

@tidwall
Copy link
Owner

tidwall commented Jul 14, 2024

One other thing. The WITHIN command as shown is attempting to find objects from the “geofences” collection that are “within” a POINT. That may not give the results that you want.

If what you are trying to do is to find which polygon geofences contain a specific point then I recommend using the INTERSECTS instead.

@hitchanatanael
Copy link
Author

Previously I had also changed from longitude latitude to longitude latitude, and the results were still the same.

here is the generated laravel log:
[2024-07-15 06:00:38] local.INFO: Successfully connected to Tile38
[2024-07-15 06:00:38] local.INFO: Tile38 set response {"response":"OK"}
[2024-07-15 06:00:38] local.INFO: Tile38 response {"response":[0,[]]}
[2024-07-15 06:00:38] local.INFO: User is outside the geofence.

protected $tile38;

public function __construct()
{
    try {
        $this->tile38 = new Client([
            'scheme' => 'tcp',
            'host' => config('database.redis.tile38.host'),
            'port' => config('database.redis.tile38.port'),
        ]);
        Log::info('Successfully connected to Tile38');
    } catch (\Exception $e) {
        Log::error('Failed to connect to Tile38: ' . $e->getMessage());
    }
}

private function checkGeofence($latitude, $longitude)
{
    try {
        $pingResponse = $this->tile38->ping();
        if ($pingResponse->getPayload() !== 'PONG') {
            Log::error('Tidak dapat terhubung ke Tile38.');
            return ['status' => false, 'message' => 'Tidak dapat terhubung ke Tile38.'];
        }

        $geoJson = '{
            "type" : "Polygon",
            "coordinates" : [
                [
                    [101.3629689712017, 0.46937355787708773],
                    [101.36295220739564, 0.46947011415901285],
                    [101.36297098285843, 0.46951839229948555],
                    [101.3630427319483, 0.46955057772626807],
                    [101.36313727981437, 0.4695324734237228],
                    [101.36315069085921, 0.46947883104552296],
                    [101.36316141969506, 0.4694332350237229],
                    [101.36313258594868, 0.46935210092537777],
                    [101.36299713439585, 0.46932527973563537],
                    [101.3629689712017, 0.46937355787708773]
                ]
            ]
        }';

        $geoJsonString = json_encode(json_decode($geoJson, true));

        $setCommand = ['SET', 'geofence', 'mygeofence', 'OBJECT', $geoJsonString];
        $setResponse = $this->tile38->executeRaw($setCommand);
        Log::info('Tile38 set response', ['response' => $setResponse]);

        $withinCommand = ['WITHIN', 'geofence', 'POINT', $latitude, $longitude];
        $response = $this->tile38->executeRaw($withinCommand);
        Log::info('Tile38 response', ['response' => $response]);

        if (isset($response[0]) && $response[0] === 'OK' && count($response) > 1) {
            return ['status' => 'success', 'isWithin' => true];
        }

        return ['status' => 'error', 'message' => 'You are outside the geofence area'];
    } catch (\Exception $e) {
        Log::error('Error in Geofence check: ' . $e->getMessage());
        return ['status' => 'error', 'message' => 'Internal Server Error'];
    }
}

@hitchanatanael
Copy link
Author

Is there a Laravel project that uses tile38 before? I've looked for several sources, and haven't found anyone who has used Laravel. Maybe you have other references, and if you use another programming language that's okay, as long as you use set and within queries. because maybe I still have errors in making the query

@iwpnd
Copy link
Contributor

iwpnd commented Jul 15, 2024

Switch WITHIN with INTERSECTS and try again as suggested above. 🙂

A Polygon will never be WITHIN a point, but a Point can INTERSECT a Polygon.

@hitchanatanael
Copy link
Author

Wow, I am very grateful, finally my project was successfully completed. For geofencing, do I need to send it in this comment? I will immediately send the Laravel controller for geofencing here. once again thank you all

@iwpnd
Copy link
Contributor

iwpnd commented Jul 16, 2024

For geofencing, do I need to send it in this comment?

To setup a geofence from that polygon you would do

SETHOOK {your_geofence} {endpoint to send events to} INTERSECT {the collection you want to alert on} OBJECT {your polygon}

// if you already have the geofence in your collection you can also do it like this
// to reuse the geometry you have already stored.
SETHOOK {name of your geofence} {endpoint where to send events to} INTERSECT {the collection you want to alert on} GET geofence mygeofence

e.g. to alert on vehicles in the fleet collection entering your geofence and sending to an https endpoint you would do

SETHOOK mygeofence https://localhost:3000/my_hook INTERSECTS fleet GET geofence mygeofence

@hitchanatanael
Copy link
Author

My project using Laravel and Tile38 has been completed, and I also use INTERSECTS according to your suggestion to check whether the user is in the geofencing. Below is the complete code for geofencing:

private function checkGeofence($latitude, $longitude)
{
try {
    $pingResponse = $this->tile38->ping();
    if ($pingResponse->getPayload() !== 'PONG') {
        Log::error('Cannot connect to Tile38.');
        return ['status' => false, 'message' => 'Cannot connect to Tile38.'];
    }

    $geoJson = '{
    "type" : "Polygon",
    "coordinates" : [
        [
            [Fill in the coordinates in the order longitude, latitude],
        ]
    ]
}';

    $geoJsonString = json_encode(json_decode($geoJson, true));

    $setCommand = ['SET', 'geofence', 'mygeofence', 'OBJECT', $geoJsonString];
    $setResponse = $this->tile38->executeRaw($setCommand);
    Log::info('Tile38 set response', ['setResponse' => $setResponse]);

    $intersectsCommand = ['INTERSECTS', 'geofence', 'POINT', $latitude, $longitude];
    $intersectsResponse = $this->tile38->executeRaw($intersectsCommand);
    Log::info('Tile38 response', ['intersectsResponse' => $intersectsResponse]);

    if (isset($intersectsResponse[1]) && is_array($intersectsResponse[1]) && count($intersectsResponse[1]) > 0) {
        foreach ($intersectsResponse[1] as $result) {
            if ($result[0] === 'mygeofence') {
                return ['status' => 'success', 'isWithin' => true];
            }
        }
    }

    return ['status' => 'error', 'message' => 'You are outside the geofence area'];
} catch (\Exception $e) {
    Log::error('Error in Geofence check: ' . $e->getMessage());
    return ['status' => 'error', 'message' => 'Internal Server Error'];
}

}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants