I am trying to implement a Geofencing system using polygon shape. Basically if an user enters the geofencing region, the user should get a notification. After going through many research i was only able to find Geofence using Circular. So far i implemented the system but it only monitors by circular shape not if someone enters the polygon drawn in the map. If any has done Polygon Geonfencing before please help me out
This is the code i used to draw my polygon
private void drawGeofence() {
Log.d(TAG, "drawGeofence()");
polyLatLng = new ArrayList<>( );
polyLatLng.add( new LatLng( 6.895450, 79.852170 ) ); // Should match last point
polyLatLng.add( new LatLng(6.897287, 79.859544));
polyLatLng.add( new LatLng( 6.905271, 79.862609 ) );
polyLatLng.add( new LatLng( 6.906114, 79.858998 ) );
polyLatLng.add( new LatLng( 6.911808, 79.856206 ) );
polyLatLng.add( new LatLng( 6.912200, 79.851381 ) );
polyLatLng.add( new LatLng( 6.911627, 79.849621 ) );
polyLatLng.add( new LatLng( 6.910965, 79.848073 ) );
polyLatLng.add( new LatLng( 6.895450, 79.852170 ) ); // Should match first point
Log.i(TAG, "computeArea " + SphericalUtil.computeArea(polyLatLng));
map.addPolygon(new PolygonOptions()
.addAll(polyLatLng)
.strokeColor(Color.BLACK)
.strokeWidth( 4 )
.fillColor(0x220000FF));
}
Here's my geofence code which track only in circular region
private static final float GEOFENCE_RADIUS = 1006.3975694699f; // in meters
private void startGeofence() {
Log.i(TAG, "startGeofence()");
if( geoFenceMarker != null ) {
// create geofence
Geofence geofence = createGeofence( 6.904254, 79.853798, GEOFENCE_RADIUS );
GeofencingRequest geofenceRequest = createGeofenceRequest( geofence );
addGeofence( geofenceRequest );
} else {
Log.e(TAG, "Geofence marker is null");
}
}
// Create a Geofence
private Geofence createGeofence( double lat, double lng, float radius ) {
Log.d(TAG, "createGeofence");
return new Geofence.Builder()
.setRequestId(GEOFENCE_REQ_ID)
.setCircularRegion( lat, lng, radius)
.setExpirationDuration( GEO_DURATION )
.setTransitionTypes( Geofence.GEOFENCE_TRANSITION_ENTER
| Geofence.GEOFENCE_TRANSITION_EXIT )
.build();
}enter code here
Could you use this function from geopandas : https://geopandas.org/en/stable/docs/reference/api/geopandas.GeoSeries.union.html
If you isolate each polygon of your Multipolygon and merge them with this it should work isn't it ?
Related
I have a class which is inherited from QLineEdit, and I set an icon as an action button for this.
MyEdit::MyEdit( QWidget *p_parent ) : QLineEdit( p_parent )
{
m_buttonAction = addAction( QIcon( "search.png" ), QLineEdit::TrailingPosition );
QAbstractButton *button = qobject_cast<QAbstractButton *>( m_buttonAction->associatedWidgets().last() );
m_buttonAction->setVisible( false );
connect( m_buttonAction, &QAction::triggered, this, &MyEdit::openCompleter );
m_completer = new QCompleter( this );
m_sourceModel = new CompleterSourceModel( m_completer );
m_view = new CompleterView();
m_view->setStyle( &m_style );
m_delegate = new CompleterDelegate( m_view );
m_completer->setPopup( m_view );
m_completer->setModel( m_sourceModel );
m_view->setItemDelegate( m_delegate );
setCompleter( m_completer );
}
void MyEdit::setDataForCompleter( const CompleterData &p_data )
{
m_sourceModel->setCompleterData( p_data );
m_buttonAction->setVisible( p_data.data().size() > 0 );
}
When I import data for completer, the icon is always shown. Now I need to hide this icon in case MyEdit is disabled or as ReadOnly.
I am thinking about override setDisabled and setReadOnly for my class, and in there setVisible for the icon. But these functions are not virtual, so can not be overridden.
I am thinking also about a signal like stateChanged of my class, so I can do it in a slot. But I can not find any signal like that for QLineEdit. Do you have any idea how to do it?
You can handle events QEvent::ReadOnlyChange or QEvent::EnabledChange by overriding the QLineEdit::event method
UPDATE:
Here is an example implementation:
bool MyEdit::event(QEvent *e) override {
const auto type = e->type();
if (type == QEvent::ReadOnlyChange || type == QEvent::EnabledChange) {
m_buttonAction->setVisible(m_sourceModel->rowCount() > 0 ? isEnabled() && isReadOnly() : false);
}
return QLineEdit::event(e);
}
I'm searching for a way to create a custom action button which allows me to make a new DataObject with pre-filled content from another DataObject. As a simple example: When I have an email and click the "answer"-button in my email-client, I get a new window with pre-filled content from the email before. I need exactly this functionality for my button. This button should appear next to each DataObject in the GridField.
So I know how to make a button and add it to my GridField (--> https://docs.silverstripe.org/en/3.2/developer_guides/forms/how_tos/create_a_gridfield_actionprovider/) and I know how to go to a new DataObject:
Controller::curr()->redirect($gridField->Link('item/new'));
I also found out that there is a duplicate function for DataObjects:
public function duplicate($doWrite = true) {
$className = $this->class;
$clone = new $className( $this->toMap(), false, $this->model );
$clone->ID = 0;
$clone->invokeWithExtensions('onBeforeDuplicate', $this, $doWrite);
if($doWrite) {
$clone->write();
$this->duplicateManyManyRelations($this, $clone);
}
$clone->invokeWithExtensions('onAfterDuplicate', $this, $doWrite);
return $clone;
}
Perhaps it's easier than I think but at the moment I just don't get how to rewrite this to get what I need. Can somebody give me a hint?
That's for sure not the cleanest solution but I think it should do the trick.
At first let's create the custom gridfield action. Here we will save all accessible records in a session and add a query string to the url so that we'll know which object we want to "clone"
public function getColumnContent($gridField, $record, $columnName) {
if(!$record->canEdit()) return;
$field = GridField_FormAction::create(
$gridField,
'clone'.$record->ID,
'Clone',
'clone',
array('RecordID' => $record->ID)
);
$values = Session::get('ClonedData');
$data = $record->data()->toMap();
if($arr = $values) {
$arr[$record->ID] = $data;
} else {
$arr = array(
$record->ID => $data
);
}
Session::set('ClonedData', $arr);
return $field->Field();
}
public function getActions($gridField) {
return array('clone');
}
public function handleAction(GridField $gridField, $actionName, $arguments, $data) {
if($actionName == 'clone') {
$id = $arguments['RecordID'];
Controller::curr()->redirect($gridField->Link("item/new/?cloneID=$id"));
}
}
after adding this new component to our gridfield,
$gridField->getConfig()->addComponent(new GridFieldCustomAction());
we'll need to bring the data into the new form. To do so, add this code directly above "return $fields" on your getCMSFields function so it will be executed every time we'll open this kind of object.
$values = Session::get('ClonedData');
if($values) {
Session::clear('ClonedData');
$json = json_encode($values);
$fields->push(LiteralField::create('ClonedData', "<div id='cloned-data' style='display:none;'>$json</div>"));
}
At the end we need to bring the content back into the fields. We'll do that with a little bit of javascript so at first you need to create a new script.js file and include it in the ss backend (or just use an existing one).
(function($) {
$('#cloned-data').entwine({
onmatch: function() {
var data = JSON.parse($(this).text()),
id = getParameterByName('cloneID');
if(id && data) {
var obj = data[id];
if(obj) {
$.each(obj, function(i, val) {
$('[name=' + i + ']').val(val);
});
}
}
}
});
// http://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript#answer-901144
function getParameterByName(name) {
name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
results = regex.exec(location.search);
return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}
})(jQuery);
And that's it ... quite tricky. Hope it will solve your problem.
I'm trying to use a ListView in my application, which has to run on a Windows tablet. The problem is that, to select multiple item on a ListView, the user has to maintain the CTRL key pressed, which is impossible on a tablet.
So my question is : Is there a way to select multiple item in a ListView with a simple click on it?
You may filter the normal mouse Click event and convert it to Ctrl+Click. Actually to Shortcut+Click since the shortcut key may differ on the platform the app run.
EventHandler<MouseEvent> eventHandler = ( event ) ->
{
if ( !event.isShortcutDown() )
{
Event.fireEvent( event.getTarget(), cloneMouseEvent( event ) );
event.consume();
}
};
listview.getSelectionModel().setSelectionMode( SelectionMode.MULTIPLE );
listview.addEventFilter( MouseEvent.MOUSE_PRESSED, eventHandler );
listview.addEventFilter( MouseEvent.MOUSE_RELEASED, eventHandler );
where cloneMouseEvent is
private MouseEvent cloneMouseEvent( MouseEvent event )
{
switch (Toolkit.getToolkit().getPlatformShortcutKey())
{
case SHIFT:
return new MouseEvent(
event.getSource(),
event.getTarget(),
event.getEventType(),
event.getX(),
event.getY(),
event.getScreenX(),
event.getScreenY(),
event.getButton(),
event.getClickCount(),
true,
event.isControlDown(),
event.isAltDown(),
event.isMetaDown(),
event.isPrimaryButtonDown(),
event.isMiddleButtonDown(),
event.isSecondaryButtonDown(),
event.isSynthesized(),
event.isPopupTrigger(),
event.isStillSincePress(),
event.getPickResult()
);
case CONTROL:
return new MouseEvent(
event.getSource(),
event.getTarget(),
event.getEventType(),
event.getX(),
event.getY(),
event.getScreenX(),
event.getScreenY(),
event.getButton(),
event.getClickCount(),
event.isShiftDown(),
true,
event.isAltDown(),
event.isMetaDown(),
event.isPrimaryButtonDown(),
event.isMiddleButtonDown(),
event.isSecondaryButtonDown(),
event.isSynthesized(),
event.isPopupTrigger(),
event.isStillSincePress(),
event.getPickResult()
);
case ALT:
return new MouseEvent(
event.getSource(),
event.getTarget(),
event.getEventType(),
event.getX(),
event.getY(),
event.getScreenX(),
event.getScreenY(),
event.getButton(),
event.getClickCount(),
event.isShiftDown(),
event.isControlDown(),
true,
event.isMetaDown(),
event.isPrimaryButtonDown(),
event.isMiddleButtonDown(),
event.isSecondaryButtonDown(),
event.isSynthesized(),
event.isPopupTrigger(),
event.isStillSincePress(),
event.getPickResult()
);
case META:
return new MouseEvent(
event.getSource(),
event.getTarget(),
event.getEventType(),
event.getX(),
event.getY(),
event.getScreenX(),
event.getScreenY(),
event.getButton(),
event.getClickCount(),
event.isShiftDown(),
event.isControlDown(),
event.isAltDown(),
true,
event.isPrimaryButtonDown(),
event.isMiddleButtonDown(),
event.isSecondaryButtonDown(),
event.isSynthesized(),
event.isPopupTrigger(),
event.isStillSincePress(),
event.getPickResult()
);
default: // well return itself then
return event;
}
}
I am looking for an example to restrict user input to only digits and decimal points using the new class TextFormatter of Java8 u40.
http://download.java.net/jdk9/jfxdocs/javafx/scene/control/TextFormatter.Change.html
Please see this example:
DecimalFormat format = new DecimalFormat( "#.0" );
TextField field = new TextField();
field.setTextFormatter( new TextFormatter<>(c ->
{
if ( c.getControlNewText().isEmpty() )
{
return c;
}
ParsePosition parsePosition = new ParsePosition( 0 );
Object object = format.parse( c.getControlNewText(), parsePosition );
if ( object == null || parsePosition.getIndex() < c.getControlNewText().length() )
{
return null;
}
else
{
return c;
}
}));
Here I used the TextFormatter(UnaryOperator filter) constructor which takes a filter only as a parameter.
To understand the if-statement refer to DecimalFormat parse(String text, ParsePosition pos).
Generally I can get this to work no problem when I reimplement QTableView::mousePressEvent( QMouseEvent* ). However, doing it on QHeaderView is not working for me. Code is simple.
void my_header_t::mousePressEvent( QMouseEvent* event )
{
if ( !event ) {
return;
}
if ( event->button() == Qt::RightButton ) {
QPoint point( event->x(), event->y() );
QModelIndex index = indexAt( point );
printf( "%s data %s %d,%d %s (point: %d,%d )\n",
ts().c_str(), index.data().toString().toStdString().c_str(),
index.row(), index.column(), index.isValid() ? "True" : "False",
event->x(), event->y() );
handle_right_click( index.data().toString() );
} else {
QHeaderView::mousePressEvent( event );
}
x() and y() from the QMouseEvent are fine. However, it creates an invalid index, with row() of -1, and column() of -1. Obviously, I'm passing an empty string to handle_right_click() which kicks off a menu. That menu is not going to know which column called it, and the mayhem will further ensue.
I know that clicked( const QModelIndex& ) will just tell me the right index, with the text. However, I need to differentiate between buttons.
QHeaderView provides an alternative function, logicalIndexAt, for determining the index of the header item that you're interested in. Using your code from above:
void my_header_t::mousePressEvent( QMouseEvent* event )
{
if ( !event ) {
return;
}
if ( event->button() == Qt::RightButton ) {
int index = logicalIndexAt( event->pos() );
handle_right_click(model()->headerData(index, Qt::Horizontal).toString());
} else {
QHeaderView::mousePressEvent( event );
}
}
Note that the orientation of the header must be passed to the headerData method (in this case, I've just assumed that it's Qt::Horizontal, but in your case it might be something different).