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

Add support for relative filtering #4740

Merged
merged 9 commits into from Dec 27, 2021
Merged

Conversation

soshial
Copy link
Contributor

@soshial soshial commented Sep 10, 2021

This PR tries to address problem #4735.

@soshial soshial changed the title Add support for relative filtering Add support for relative filtering (#4735) Sep 10, 2021
@soshial soshial changed the title Add support for relative filtering (#4735) Add support for relative filtering Sep 10, 2021
}

Position previous = Context.getDataManager().getPrevPosition(position.getDeviceId(), newfixTime);
Position next = Context.getDataManager().getNextPosition(position.getDeviceId(), newfixTime);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please provide details on why we need "next" position.

Copy link
Contributor Author

@soshial soshial Sep 10, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The backend has configured MinPeriod filter set to 2 seconds. Imagine that these Positions were received by the server:

  • Position A: fixTime = 18:18:01
  • Position C: fixTime = 18:18:05

Then after 10 seconds, server receives position B with fixTime = 18:18:04. As it turns out, it was not delivered immediately because device was offline for 3 seconds, because it was switching between networks.

Time-wise position B is far enough from position A (previous location), but it's too close to position C (next position). Hence, we need to take into account next position as well.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes sense in theory, but I'm not sure if it makes sense to actually implement it. You basically make 3 expensive SQL requests for every location report. It will cripple pretty much any server that runs any significant number of devices.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that with thousands of tracking devices this will have deteriorating impact. But for private use it will not be a problem. No doubt, that we should make this option disabled by default.

What do you say, if I introduce this optimization: let's load next and prev positions only in case any of these filters are turned active:

  • filterDistance
  • filterMaxSpeed
  • filterMinPeriod

So if we only duplicate filter is active, than we have only 1 request instead of 3.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm mostly worried about value of this feature vs cost of maintaining it going forward.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can also make lazy loading of last / previous position. What do you say? Only in the case when at least one of the corresponding filters is activated.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense to me.

Copy link
Contributor Author

@soshial soshial Sep 11, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check this out:

line 149: moved skipAttr to the top
line 176: lazy instantiation of previous

image

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks fine, but please update PR so I can review it more thoroughly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I have pushed a new commit.

Copy link
Member

@tananaev tananaev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. Add a few minor comments, mostly about formatting.

src/main/java/org/traccar/handler/FilterHandler.java Outdated Show resolved Hide resolved
src/main/java/org/traccar/handler/FilterHandler.java Outdated Show resolved Hide resolved
src/main/java/org/traccar/handler/FilterHandler.java Outdated Show resolved Hide resolved
src/main/java/org/traccar/handler/FilterHandler.java Outdated Show resolved Hide resolved
src/main/java/org/traccar/config/Keys.java Outdated Show resolved Hide resolved
src/main/java/org/traccar/database/DataManager.java Outdated Show resolved Hide resolved
setup/default.xml Outdated Show resolved Hide resolved
setup/default.xml Outdated Show resolved Hide resolved
src/main/java/org/traccar/handler/FilterHandler.java Outdated Show resolved Hide resolved
src/main/java/org/traccar/handler/FilterHandler.java Outdated Show resolved Hide resolved
@soshial
Copy link
Contributor Author

soshial commented Sep 13, 2021 via email

@@ -48,6 +48,10 @@
SELECT * FROM tc_positions WHERE deviceId = :deviceId AND fixTime BETWEEN :from AND :to ORDER BY fixTime
</entry>

<entry key='database.selectPrecedingPosition'>
SELECT * FROM tc_positions WHERE deviceId = :deviceId AND fixTime lte :time ORDER BY fixTime DESC LIMIT 1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One last thing. I noticed that you are using lte. Is it a standard SQL syntax that will work in every SQL server? Can you please provide a reference because I couldn't find any information about it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly, I thought that you were using some kind of library to parse SQL stored in XML. Did you figure out correctly, that you wrote it yourself. I couldn't use < sign since it's forbidden inside XML tag.

We may use this:
SELECT * FROM tc_positions WHERE deviceId = :deviceId AND fixTime &lt;= :time ORDER BY fixTime DESC LIMIT 1
or
SELECT * FROM tc_positions WHERE deviceId = :deviceId AND NOT (fixTime > :time) ORDER BY fixTime DESC LIMIT 1

What do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think just &lt;= should be fine, so let's go with option 1.

Copy link
Member

@tananaev tananaev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, just to confirm. Have you tested the latest to make sure everything works?

Comment on lines 335 to 340
.executeQuery(Position.class);
if (positions.isEmpty()) {
return null;
} else {
return positions.iterator().next();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, just noticed another thing. Can we use executeQuerySingle here instead?

@soshial
Copy link
Contributor Author

soshial commented Sep 18, 2021

I have tested the code and I found a serious problem (as it seems to me), that have existed in traccar even before my pull request. As it turns out, traccar doesn't save fractional TIMESTAMP values, it saves only seconds (not milliseconds). Which means that duplicate comparison didn't work for protocols that send milliseconds:

position.getFixTime().getTime() == last.getFixTime().getTime() returns false for seemingly duplicate position (that I send repeatedly). When comparing to last received position (as was implemented before) this problem did not appear (because positions were kept inside IdentityManager in RAM). But if we take values from DB that cuts off milliseconds, it becomes a problem.

Compare:

  • the new received position had value 1626692627800
  • and the position from DB has value 1626692627000

Honestly, I really think that traccar should save milliseconds in the database. Some usecases demand location data more frequent than once per second.

Something like this: ALTER TABLE tc_positions CHANGE COLUMN fixtime TIMESTAMP(6) and other time-related columns.

@tananaev
Copy link
Member

What database are you using?

@soshial
Copy link
Contributor Author

soshial commented Sep 18, 2021 via email

@tananaev
Copy link
Member

Is there a universal solution?

@soshial
Copy link
Contributor Author

soshial commented Sep 18, 2021 via email

@tananaev
Copy link
Member

I mean any ideas how to fix it via liquibase, so it's supported for add databases?

@soshial
Copy link
Contributor Author

soshial commented Sep 18, 2021

Looks like this should do the thing:

<modifyDataType tableName="tc_positions" columnName="fixtime" newDataType="TIMESTAMP(6)" />
<modifyDataType tableName="tc_positions" columnName="servertime" newDataType="TIMESTAMP(6)" />
<modifyDataType tableName="tc_positions" columnName="devicetime" newDataType="TIMESTAMP(6)" />
<modifyDataType tableName="tc_events" columnName="servertime" newDataType="TIMESTAMP(6)" />
<modifyDataType tableName="tc_devices" columnName="lastupdate" newDataType="TIMESTAMP(6)" />

How do check that it works?

@tananaev
Copy link
Member

You have to check if it works with different database types. At least H2, MySQL, MS SQL and PostgreSQL.

@soshial
Copy link
Contributor Author

soshial commented Sep 18, 2021

Let's switch to another PR? (since it's a different bug fix)

How do I "launch" this new SQL changeset?

@tananaev
Copy link
Member

Yep, makes sense to do a separate PR.

@soshial
Copy link
Contributor Author

soshial commented Sep 18, 2021

Besides this separate time issue, I would say everything works for me.

}

private boolean filterZero(Position position) {
return filterZero && position.getLatitude() == 0.0 && position.getLongitude() == 0.0;
}

private boolean filterDuplicate(Position position, Position last) {
if (filterDuplicate && last != null && position.getFixTime().equals(last.getFixTime())) {
if (filterDuplicate && last != null && position.getFixTime().getTime() == last.getFixTime().getTime()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're going to store milli/micro/nanoseconds in the TIMESTAMP field in the DB, then it means we would in fact compare float numbers. So maybe to filter out duplicates we should compare getTime() with a specified precision.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, where did you get floating numbers and nanoseconds? As far as I can tell, equals does exactly the same thing as you do manually:

https://docs.oracle.com/javase/8/docs/api/java/util/Date.html#equals-java.lang.Object-

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, currently it does the same thing. But, if we make this change #4746, then database would store time with milliseconds/nanosecond precision (as float). Hence, comparing new position (converted from text) and preceding (from DB) might not be equal in theory. That is why I suggest to do comparison with a certain precision. I can do additional commit for that. Do you agree?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought you planned to fix database accuracy?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's leave it as it is now. And deal with it in the separate PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree but this change should be reverted because it doesn't really do anything.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@tsmgeek
Copy link
Contributor

tsmgeek commented Sep 19, 2021

Note with moving to fractional seconds you may need to account for BETWEEN queries (from UI etc) that may be off by 0.999999 seconds.
Ie between 00:01-00:02 when actually you may have meant 00:01.000000-00:02.999999.

In some apps at work we just use https://docs.oracle.com/javase/8/docs/api/java/time/LocalDateTime.html#MAX in code to always push max microseconds to account for this.

Position last = null;
if (Context.getIdentityManager() != null) {
last = Context.getIdentityManager().getLastPosition(position.getDeviceId());
if (skipAttributes(position)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this actually changes the filtering logic. We only want to apply skipLimit and skipAttributes for certain types of filters.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. Could you please elaborate? Why should those 2 skips work only for some filters?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was a forum discussion about it if you want more context. Basically there are two categories of filtering. The ones that filter definitely invalid data (e.g. zero coordinates) and the ones that filter excessive data (e.g. duplicates). We always want to filter former, but want to keep latter if it has some important information (e.g. alarms).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. I reverted to the original logic and also preserved almost the same order of filter checks (as it is in current master branch).


// filter out excessive data
long deviceId = position.getDeviceId();
if (filterDuplicate || filterDistance > 0 || filterMaxSpeed > 0 || filterMinPeriod > 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about filterStatic?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

@tananaev tananaev merged commit 790db62 into traccar:master Dec 27, 2021
@tananaev
Copy link
Member

Merged, thank you.

@soshial
Copy link
Contributor Author

soshial commented Dec 30, 2021

@tananaev: Could you please add the new filter.relative key to the documentation?

@tananaev
Copy link
Member

The plan is to replace that documentation with something that's generated automatically.

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

Successfully merging this pull request may close these issues.

None yet

3 participants