aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--default.cfg40
-rw-r--r--opengts.cfg1
-rw-r--r--src/index.html796
-rw-r--r--src/org/traccar/Server.java14
-rw-r--r--src/org/traccar/helper/NamedParameterStatement.java13
-rw-r--r--src/org/traccar/http/WebServer.java53
-rw-r--r--src/org/traccar/model/DataManager.java4
-rw-r--r--src/org/traccar/model/DatabaseDataManager.java101
8 files changed, 966 insertions, 56 deletions
diff --git a/default.cfg b/default.cfg
index e16223dfd..3c432d092 100644
--- a/default.cfg
+++ b/default.cfg
@@ -15,8 +15,8 @@
<!-->
Create database:
- CREATE TABLE devices (id INT, imei VARCHAR(16))
- CREATE TABLE positions (device_id INT, time TIMESTAMP, is_valid BOOLEAN, latitude DOUBLE, longitude DOUBLE, speed DOUBLE, course DOUBLE, power DOUBLE)
+ CREATE TABLE devices (id INT IDENTITY imei VARCHAR(16))
+ CREATE TABLE positions (device_id INT, time TIMESTAMP, valid BOOLEAN, latitude DOUBLE, longitude DOUBLE, speed DOUBLE, course DOUBLE, power DOUBLE)
<-->
<!-->
@@ -29,6 +29,41 @@
</entry>
<!-->
+ imei - String
+ <-->
+ <entry key="database.insertDevice">
+ INSERT INTO devices (imei)
+ VALUES (:imei)
+ </entry>
+
+ <!-->
+ id - Integer
+ imei - String
+ <-->
+ <entry key="database.updateDevice">
+ UPDATE devices
+ SET imei = :imei
+ WHERE id = :id
+ </entry>
+
+ <!-->
+ id - Integer
+ <-->
+ <entry key="database.deleteDevice">
+ DELETE FROM devices
+ WHERE id = :id
+ </entry>
+
+ <!-->
+ device_id - Integer
+ <-->
+ <entry key="database.selectPosition">
+ SELECT *
+ FROM positions
+ WHERE device_id = :device_id
+ </entry>
+
+ <!-->
device_id - Integer
time - Date
valid - Boolean
@@ -45,6 +80,7 @@
</entry>
<!-- Web interface port -->
+ <entry key="http.enable">true</entry>
<entry key="http.port">8082</entry>
<!-- Logging options -->
diff --git a/opengts.cfg b/opengts.cfg
index 075dd12e5..40f535723 100644
--- a/opengts.cfg
+++ b/opengts.cfg
@@ -41,6 +41,7 @@
</entry>
<!-- Web interface port -->
+ <entry key="http.enable">true</entry>
<entry key="http.port">8082</entry>
<!-- Logging options -->
diff --git a/src/index.html b/src/index.html
index 1b782db82..cde73f47b 100644
--- a/src/index.html
+++ b/src/index.html
@@ -5,8 +5,747 @@
<link rel="stylesheet" type="text/css" href="http://cdn.sencha.io/ext-4.1.0-gpl/resources/css/ext-all.css" />
<script type="text/javascript" src="http://cdn.sencha.io/ext-4.1.0-gpl/ext-all.js"></script>
-<!-- WARNING: integrate to page -->
-<!-- <script type="text/javascript" src="https://raw.github.com/VinylFox/ExtJS.ux.GMapPanel/master/src/GMapPanel3.js"></script> -->
+<!-- check for new version: https://raw.github.com/VinylFox/ExtJS.ux.GMapPanel/master/src/GMapPanel3.js -->
+
+<script type="text/javascript">
+Ext.ns('Ext.ux');
+/**
+ * @class Ext.ux.GMapPanel
+ * @extends Ext.Panel
+ * @author Shea Frederick
+ */
+Ext.define('Ext.ux.GMapPanel', {
+
+ extend: 'Ext.panel.Panel',
+
+ alias: 'widget.gmappanel',
+
+ requires: ['Ext.window.MessageBox'],
+ /**
+ * @cfg {Boolean} border
+ * Defaults to <tt>false</tt>. See {@link Ext.Panel}.<code>{@link Ext.Panel#border border}</code>.
+ */
+ border: false,
+
+ /**
+ * @cfg {Array} respErrors
+ * An array of msg/code pairs.
+ */
+ respErrors: [{
+ code: 'UNKNOWN_ERROR',
+ msg: 'A geocoding or directions request could not be successfully processed, yet the exact reason for the failure is not known.'
+ },{
+ code: 'ERROR',
+ msg: 'There was a problem contacting the Google servers.'
+ },{
+ code: 'ZERO_RESULTS',
+ msg: 'The request did not encounter any errors but returns zero results.'
+ },{
+ code: 'INVALID_REQUEST',
+ msg: 'This request was invalid.'
+ },{
+ code: 'REQUEST_DENIED',
+ msg: 'The webpage is not allowed to use the geocoder for some reason.'
+ },{
+ code: 'OVER_QUERY_LIMIT',
+ msg: 'The webpage has gone over the requests limit in too short a period of time.'
+ }],
+ /**
+ * @cfg {Array} locationTypes
+ * An array of msg/code/level pairs.
+ */
+ locationTypes: [{
+ level: 4,
+ code: 'ROOFTOP',
+ msg: 'The returned result is a precise geocode for which we have location information accurate down to street address precision.'
+ },{
+ level: 3,
+ code: 'RANGE_INTERPOLATED',
+ msg: 'The returned result reflects an approximation (usually on a road) interpolated between two precise points (such as intersections). Interpolated results are generally returned when rooftop geocodes are unavailable for a street address.'
+ },{
+ level: 2,
+ code: 'GEOMETRIC_CENTER',
+ msg: 'The returned result is the geometric center of a result such as a polyline (for example, a street) or polygon (region).'
+ },{
+ level: 1,
+ code: 'APPROXIMATE',
+ msg: 'The returned result is approximate.'
+ }],
+ /**
+ * @cfg {String} respErrorTitle
+ * Defaults to <tt>'Error'</tt>.
+ */
+ respErrorTitle : 'Error',
+ /**
+ * @cfg {String} geoErrorMsgUnable
+ * Defaults to <tt>'Unable to Locate the Address you provided'</tt>.
+ */
+ geoErrorMsgUnable : 'Unable to Locate the Address you provided',
+ /**
+ * @cfg {String} geoErrorTitle
+ * Defaults to <tt>'Address Location Error'</tt>.
+ */
+ geoErrorTitle : 'Address Location Error',
+ /**
+ * @cfg {String} geoErrorMsgAccuracy
+ * Defaults to <tt>'The address provided has a low accuracy.<br><br>{0} Accuracy.'</tt>.
+ * <div class="mdetail-params"><ul>
+ * <li><b><code>ROOFTOP</code></b> : <div class="sub-desc"><p>
+ * The returned result is a precise geocode for which we have location information accurate down to street address precision.
+ * </p></div></li>
+ * <li><b><code>RANGE_INTERPOLATED</code></b> : <div class="sub-desc"><p>
+ * The returned result reflects an approximation (usually on a road) interpolated between two precise points (such as intersections). Interpolated results are generally returned when rooftop geocodes are unavailable for a street address.
+ * </p></div></li>
+ * <li><b><code>GEOMETRIC_CENTER</code></b> : <div class="sub-desc"><p>
+ * The returned result is the geometric center of a result such as a polyline (for example, a street) or polygon (region).
+ * </p></div></li>
+ * <li><b><code>APPROXIMATE</code></b> : <div class="sub-desc"><p>
+ * The returned result is approximate.
+ * </p></div></li>
+ * </ul></div>
+ */
+ geoErrorMsgAccuracy : 'The address provided has a low accuracy.<br><br>"{0}" Accuracy.<br><br>{1}',
+ /**
+ * @cfg {String} gmapType
+ * The type of map to display, generic options available are: 'map', 'panorama'.
+ * Defaults to <tt>'map'</tt>.
+ * More specific maps can be used by specifying the google map type:
+ * <div class="mdetail-params"><ul>
+ * <li><b><code>G_NORMAL_MAP</code></b> : <div class="sub-desc"><p>
+ * Displays the default road map view
+ * </p></div></li>
+ * <li><b><code>G_SATELLITE_MAP</code></b> : <div class="sub-desc"><p>
+ * Displays Google Earth satellite images
+ * </p></div></li>
+ * <li><b><code>G_HYBRID_MAP</code></b> : <div class="sub-desc"><p>
+ * Displays a mixture of normal and satellite views
+ * </p></div></li>
+ * <li><b><code>G_DEFAULT_MAP_TYPES</code></b> : <div class="sub-desc"><p>
+ * Contains an array of the above three types, useful for iterative processing.
+ * </p></div></li>
+ * <li><b><code>G_PHYSICAL_MAP</code></b> : <div class="sub-desc"><p>
+ * Displays a physical map based on terrain information.
+ * </p></div></li>
+ * <li><b><code>G_MOON_ELEVATION_MAP</code></b> : <div class="sub-desc"><p>
+ * Displays a shaded terrain map of the surface of the Moon, color-coded by altitude.
+ * </p></div></li>
+ * <li><b><code>G_MOON_VISIBLE_MAP</code></b> : <div class="sub-desc"><p>
+ * Displays photographic imagery taken from orbit around the moon.
+ * </p></div></li>
+ * <li><b><code>G_MARS_ELEVATION_MAP</code></b> : <div class="sub-desc"><p>
+ * Displays a shaded terrain map of the surface of Mars, color-coded by altitude.
+ * </p></div></li>
+ * <li><b><code>G_MARS_VISIBLE_MAP</code></b> : <div class="sub-desc"><p>
+ * Displays photographs taken from orbit around Mars.
+ * </p></div></li>
+ * <li><b><code>G_MARS_INFRARED_MAP</code></b> : <div class="sub-desc"><p>
+ * Displays a shaded infrared map of the surface of Mars, where warmer areas appear brighter and colder areas appear darker.
+ * </p></div></li>
+ * <li><b><code>G_SKY_VISIBLE_MAP</code></b> : <div class="sub-desc"><p>
+ * Displays a mosaic of the sky, as seen from Earth, covering the full celestial sphere.
+ * </p></div></li>
+ * </ul></div>
+ * Sample usage:
+ * <pre><code>
+ * gmapType: G_MOON_VISIBLE_MAP
+ * </code></pre>
+ */
+ gmapType : 'map',
+ /**
+ * @cfg {Object} setCenter
+ * The initial center location of the map. The map needs to be centered before it can be used.
+ * A marker is not required to be specified.
+ * More markers can be added to the map using the <code>{@link #markers}</code> array.
+ * For example:
+ * <pre><code>
+setCenter: {
+ geoCodeAddr: '4 Yawkey Way, Boston, MA, 02215-3409, USA',
+ marker: {title: 'Fenway Park'}
+},
+
+// or just specify lat/long
+setCenter: {
+ lat: 42.345573,
+ lng: -71.098326
+}
+ * </code></pre>
+ */
+ /**
+ * @cfg {Number} zoomLevel
+ * The zoom level to initialize the map at, generally between 1 (whole planet) and 40 (street).
+ * Also used as the zoom level for panoramas, zero specifies no zoom at all.
+ * Defaults to <tt>3</tt>.
+ */
+ zoomLevel: 3,
+ /**
+ * @cfg {Number} yaw
+ * The Yaw, or rotational direction of the users perspective in degrees. Only applies to panoramas.
+ * Defaults to <tt>180</tt>.
+ */
+ yaw: 180,
+ /**
+ * @cfg {Number} pitch
+ * The pitch, or vertical direction of the users perspective in degrees.
+ * Defaults to <tt>0</tt> (straight ahead). Valid values are between +90 (straight up) and -90 (straight down).
+ */
+ pitch: 0,
+ /**
+ * @cfg {Boolean} displayGeoErrors
+ * True to display geocoding errors to the end user via a message box.
+ * Defaults to <tt>false</tt>.
+ */
+ displayGeoErrors: false,
+ /**
+ * @cfg {Boolean} minGeoAccuracy
+ * The level to display an accuracy error below. Defaults to <tt>ROOFTOP</tt>. For additional information
+ * see <a href="http://code.google.com/apis/maps/documentation/reference.html#GGeoAddressAccuracy">here</a>.
+ */
+ minGeoAccuracy: 'ROOFTOP',
+ /**
+ * @cfg {Array} mapConfOpts
+ * Array of strings representing configuration methods to call, a full list can be found
+ * <a href="http://code.google.com/apis/maps/documentation/reference.html#GMap2">here</a>.
+ * For example:
+ * <pre><code>
+ * mapConfOpts: ['enableScrollWheelZoom','enableDoubleClickZoom','enableDragging'],
+ * </code></pre>
+ */
+ /**
+ * @cfg {Array} mapControls
+ * Array of strings representing map controls to initialize, a full list can be found
+ * <a href="http://code.google.com/apis/maps/documentation/reference.html#GControlImpl">here</a>.
+ * For example:
+ * <pre><code>
+ * mapControls: ['GSmallMapControl','GMapTypeControl','NonExistantControl']
+ * </code></pre>
+ */
+ /**
+ * @cfg {Array} markers
+ * Markers may be added to the map. Instead of specifying <code>lat</code>/<code>lng</code>,
+ * geocoding can be specified via a <code>geoCodeAddr</code> string.
+ * For example:
+ * <pre><code>
+markers: [{
+ //lat: 42.339641,
+ //lng: -71.094224,
+ // instead of lat/lng:
+ geoCodeAddr: '465 Huntington Avenue, Boston, MA, 02215-5597, USA',
+ marker: {title: 'Boston Museum of Fine Arts'},
+ listeners: {
+ click: function(e){
+ Ext.Msg.alert('Its fine', 'and its art.');
+ }
+ }
+},{
+ lat: 42.339419,
+ lng: -71.09077,
+ marker: {title: 'Northeastern University'}
+}]
+ * </code></pre>
+ */
+ // private
+ mapDefined: false,
+ // private
+ mapDefinedGMap: false,
+ initComponent : function(){
+
+ this.addEvents(
+ /**
+ * @event mapready
+ * Fires when the map is ready for interaction
+ * @param {GMapPanel} this
+ * @param {GMap} map
+ */
+ 'mapready',
+ /**
+ * @event apiready
+ * Fires when the Google Maps API is loaded
+ */
+ 'apiready'
+ );
+
+ Ext.applyIf(this,{
+ markers: [],
+ cache: {
+ marker: [],
+ polyline: [],
+ infowindow: []
+ }
+ });
+
+ Ext.ux.GMapPanel.superclass.initComponent.call(this);
+
+ if (window.google && window.google.maps){
+ this.on('afterrender', this.apiReady, this);
+ }else{
+ window.gmapapiready = Ext.Function.bind(this.apiReady,this);
+ this.buildScriptTag('http://maps.google.com/maps/api/js?sensor=false&callback=gmapapiready');
+ }
+
+ },
+ apiReady : function(){
+
+ if (this.rendered){
+
+ Ext.defer(function(){
+ if (this.gmapType === 'map'){
+ this.gmap = new google.maps.Map(this.getEl().dom, {zoom:this.zoomLevel,mapTypeId: google.maps.MapTypeId.ROADMAP});
+ this.mapDefined = true;
+ this.mapDefinedGMap = true;
+ }
+
+ if (this.gmapType === 'panorama'){
+ this.gmap = new GStreetviewPanorama(this.getEl().dom);
+ this.mapDefined = true;
+ }
+
+ if (!this.mapDefined && this.gmapType){
+ this.gmap = new google.maps.Map(this.getEl().dom, {zoom:this.zoomLevel,mapTypeId: google.maps.MapTypeId.ROADMAP});
+ this.gmap.setMapTypeId(this.gmapType);
+ this.mapDefined = true;
+ this.mapDefinedGMap = true;
+ }
+
+ google.maps.event.addListenerOnce(this.getMap(), 'tilesloaded', Ext.Function.bind(this.onMapReady, this));
+ google.maps.event.addListener(this.getMap(), 'dragend', Ext.Function.bind(this.dragEnd, this));
+
+
+ if (typeof this.setCenter === 'object') {
+ if (typeof this.setCenter.geoCodeAddr === 'string'){
+ this.geoCodeLookup(this.setCenter.geoCodeAddr, this.setCenter.marker, false, true, this.setCenter.listeners);
+ }else{
+ if (this.gmapType === 'map'){
+ var point = new google.maps.LatLng(this.setCenter.lat,this.setCenter.lng);
+ this.getMap().setCenter(point, this.zoomLevel);
+ this.lastCenter = point;
+ }
+ if (typeof this.setCenter.marker === 'object' && typeof point === 'object') {
+ this.addMarker(point, this.setCenter.marker, this.setCenter.marker.clear);
+ }
+ }
+ if (this.gmapType === 'panorama'){
+ this.getMap().setLocationAndPOV(new google.maps.LatLng(this.setCenter.lat,this.setCenter.lng), {yaw: this.yaw, pitch: this.pitch, zoom: this.zoomLevel});
+ }
+ }
+ }, 200,this); // Ext.defer
+
+ }else{
+ this.on('afterrender', this.apiReady, this);
+ }
+ },
+ // private
+ afterRender : function(){
+
+ var wh = this.ownerCt.getSize();
+ Ext.applyIf(this, wh);
+
+ Ext.ux.GMapPanel.superclass.afterRender.call(this);
+
+ },
+ // private
+ buildScriptTag: function(filename, callback) {
+ var script = document.createElement('script'),
+ head = document.getElementsByTagName("head")[0];
+ script.type = "text/javascript";
+ script.src = filename;
+
+ return head.appendChild(script);
+ },
+ // private
+ onMapReady : function(){
+
+ this.addMapControls();
+ this.addOptions();
+
+ this.addMarkers(this.markers);
+ this.addMapListeners();
+
+ this.fireEvent('mapready', this, this.getMap());
+ return this;
+ },
+ // private
+ addMapListeners : function () {
+ if (this.maplisteners){
+ Ext.iterate(this.maplisteners, function(key,val){
+ google.maps.event.addListener(this.getMap(), key, Ext.Function.bind(val,this));
+ },this);
+ }
+ },
+ // private
+ onResize : function(w, h){
+
+ Ext.ux.GMapPanel.superclass.onResize.call(this, w, h);
+
+ // check for the existance of the google map in case the onResize fires too early
+ if (typeof this.getMap() == 'object') {
+ google.maps.event.trigger(this.getMap(), 'resize');
+ if (this.lastCenter){
+ this.getMap().setCenter(this.lastCenter, this.zoomLevel);
+ }
+ }
+
+ },
+ // private
+ setSize : function(width, height, animate){
+
+ Ext.ux.GMapPanel.superclass.setSize.call(this, width, height, animate);
+
+ // check for the existance of the google map in case setSize is called too early
+ if (Ext.isObject(this.getMap())) {
+ google.maps.event.trigger(this.getMap(), 'resize');
+ if (this.lastCenter){
+ this.getMap().setCenter(this.lastCenter, this.zoomLevel);
+ }
+ }
+
+ },
+ // private
+ dragEnd: function(){
+ this.lastCenter = this.getMap().getCenter();
+ },
+ /**
+ * Returns the current google map which can be used to call Google Maps API specific handlers.
+ * @return {GMap} this
+ */
+ getMap : function(){
+
+ return this.gmap;
+
+ },
+ /**
+ * Returns the maps center as a GLatLng object
+ * @return {GLatLng} this
+ */
+ getCenter : function(){
+
+ return this.getMap().getCenter();
+
+ },
+ /**
+ * Returns the maps center as a simple object
+ * @return {Object} this has lat and lng properties only
+ */
+ getCenterLatLng : function(){
+
+ var ll = this.getCenter();
+ return {lat: ll.lat(), lng: ll.lng()};
+
+ },
+ /**
+ * Creates markers from the array that is passed in. Each marker must consist of at least
+ * <code>lat</code> and <code>lng</code> properties or a <code>geoCodeAddr</code>.
+ * @param {Array} markers an array of marker objects
+ */
+ addMarkers : function(markers) {
+ if (Ext.isArray(markers)){
+ for (var i = 0; i < markers.length; i++) {
+ if (markers[i]) {
+ if (typeof markers[i].geoCodeAddr == 'string') {
+ this.geoCodeLookup(markers[i].geoCodeAddr, markers[i].marker, false, markers[i].setCenter, markers[i].listeners);
+ } else {
+ var mkr_point = new google.maps.LatLng(markers[i].lat, markers[i].lng);
+ this.addMarker(mkr_point, markers[i].marker, false, markers[i].setCenter, markers[i].listeners);
+ }
+ }
+ }
+ }
+
+ },
+ /**
+ * Creates a single marker.
+ * @param {Object} point a GLatLng point
+ * @param {Object} marker a marker object consisting of at least lat and lng
+ * @param {Boolean} clear clear other markers before creating this marker
+ * @param {Boolean} center true to center the map on this marker
+ * @param {Object} listeners a listeners config
+ */
+ addMarker : function(point, marker, clear, center, listeners){
+
+ Ext.applyIf(marker,{});
+
+ if (clear === true){
+ this.clearMarkers();
+ }
+ if (center === true) {
+ this.getMap().setCenter(point, this.zoomLevel)
+ this.lastCenter = point;
+ }
+
+ var mark = new google.maps.Marker(Ext.apply(marker, {
+ position: point
+ }));
+
+ if (marker.infoWindow){
+ this.createInfoWindow(marker.infoWindow, point, mark);
+ }
+
+ this.cache.marker.push(mark);
+ mark.setMap(this.getMap());
+
+ if (typeof listeners === 'object'){
+ for (evt in listeners) {
+ google.maps.event.addListener(mark, evt, listeners[evt]);
+ }
+ }
+
+ return mark;
+
+ },
+ /**
+ * Creates a single polyline.
+ * @param {Array} points an array of polyline points
+ * @param {Object} linestyle an object defining the line style to use
+ */
+ addPolyline : function(points, linestyle){
+
+ var plinepnts = new google.maps.MVCArray, pline, linestyle = linestyle ? linestyle : {
+ strokeColor: '#FF0000',
+ strokeOpacity: 1.0,
+ strokeWeight: 2
+ };
+
+ Ext.each(points, function(point){
+ plinepnts.push(new google.maps.LatLng(point.lat, point.lng));
+ }, this);
+
+ var pline = new google.maps.Polyline(Ext.apply({
+ path: plinepnts
+ },linestyle));
+
+ this.cache.polyline.push(pline);
+
+ pline.setMap(this.getMap());
+
+ },
+ /**
+ * Creates an Info Window.
+ * @param {Object} inwin an Info Window configuration
+ * @param {GLatLng} point the point to show the Info Window at
+ * @param {GMarker} marker a marker to attach the Info Window to
+ */
+ createInfoWindow : function(inwin, point, marker){
+
+ var me = this, infoWindow = new google.maps.InfoWindow({
+ content: inwin.content,
+ position: point
+ });
+
+ if (marker) {
+ google.maps.event.addListener(marker, 'click', function(){
+ me.hideAllInfoWindows();
+ infoWindow.open(me.getMap());
+ });
+ }
+
+ this.cache.infowindow.push(infoWindow);
+
+ return infoWindow;
+
+ },
+ // private
+ hideAllInfoWindows : function(){
+ for (var i = 0; i < this.cache.infowindow.length; i++) {
+ this.cache.infowindow[i].close();
+ }
+ },
+ // private
+ clearMarkers : function(){
+
+ this.hideAllInfoWindows();
+ this.hideMarkers();
+
+ },
+ // private
+ hideMarkers : function(){
+ Ext.each(this.cache.marker, function(mrk){
+ mrk.setMap(null);
+ });
+ },
+ // private
+ showMarkers : function(){
+ Ext.each(this.cache.marker, function(mrk){
+ mrk.setMap(this.getMap());
+ },this);
+ },
+ // private
+ addMapControls : function(){
+
+ if (this.gmapType === 'map') {
+ if (Ext.isArray(this.mapControls)) {
+ for(i=0;i<this.mapControls.length;i++){
+ //this.addMapControl(this.mapControls[i]);
+ }
+ }else if(typeof this.mapControls === 'string'){
+ //this.addMapControl(this.mapControls);
+ }else if(typeof this.mapControls === 'object'){
+ //this.getMap().add_control(this.mapControls);
+ }
+ }
+
+ },
+ /**
+ * Adds a GMap control to the map.
+ * @param {String} mc a string representation of the control to be instantiated.
+ */
+ addMapControl : function(mc){
+
+ var mcf = window[mc];
+ if (typeof mcf === 'function') {
+ //this.getMap().addControl(new mcf());
+ }
+
+ },
+ // private
+ addOptions : function(){
+
+ if (Ext.isArray(this.mapConfOpts)) {
+ var mc;
+ for(i=0;i<this.mapConfOpts.length;i++){
+ //this.addOption(this.mapConfOpts[i]);
+ }
+ }else if(typeof this.mapConfOpts === 'string'){
+ //this.addOption(this.mapConfOpts);
+ }
+
+ },
+ /**
+ * Adds a GMap option to the map.
+ * @param {String} mo a string representation of the option to be instantiated.
+ */
+ addOption : function(mo){
+
+ var mof = this.getMap()[mo];
+ if (typeof mof === 'function') {
+ this.getMap()[mo]();
+ }
+
+ },
+ /**
+ * Looks up and address and optionally add a marker, center the map to this location, or
+ * clear other markers. Sample usage:
+ * <pre><code>
+buttons: [
+ {
+ text: 'Fenway Park',
+ handler: function(){
+ var addr = '4 Yawkey Way, Boston, MA, 02215-3409, USA';
+ Ext.getCmp('my_map').geoCodeLookup(addr, undefined, false, true, undefined);
+ }
+ },{
+ text: 'Zoom Fenway Park',
+ handler: function(){
+ Ext.getCmp('my_map').zoomLevel = 19;
+ var addr = '4 Yawkey Way, Boston, MA, 02215-3409, USA';
+ Ext.getCmp('my_map').geoCodeLookup(addr, undefined, false, true, undefined);
+ }
+ },{
+ text: 'Low Accuracy',
+ handler: function(){
+ Ext.getCmp('my_map').geoCodeLookup('Paris, France', undefined, false, true, undefined);
+ }
+ },{
+
+ text: 'Bogus Address',
+ handler: function(){
+ var addr = 'Some Fake, Address, For, Errors';
+ Ext.getCmp('my_map').geoCodeLookup(addr, undefined, false, true, undefined);
+ }
+ }
+]
+ * </code></pre>
+ * @param {String} addr the address to lookup.
+ * @param {Object} marker the marker to add (optional).
+ * @param {Boolean} clear clear other markers before creating this marker
+ * @param {Boolean} center true to set this point as the center of the map.
+ * @param {Object} listeners a listeners config
+ */
+ geoCodeLookup : function(addr, marker, clear, center, listeners) {
+
+ if (!this.geocoder) {
+ this.geocoder = new google.maps.Geocoder();
+ }
+ this.geocoder.geocode({
+ address: addr
+ }, Ext.Function.bind(this.addAddressToMap, this, [addr, marker, clear, center, listeners], true));
+
+ },
+ // private
+ centerOnClientLocation : function(){
+ this.getClientLocation(function(loc){
+ var point = new google.maps.LatLng(loc.latitude,loc.longitude);
+ this.getMap().setCenter(point, this.zoomLevel);
+ this.lastCenter = point;
+ });
+ },
+ // private
+ getClientLocation : function(fn, errorFn){
+ if (!errorFn) {
+ errorFn = Ext.emptyFn;
+ }
+ if (!this.clientGeo) {
+ this.clientGeo = google.gears.factory.create('beta.geolocation');
+ }
+ geo.getCurrentPosition(Ext.Function.bind(fn, this), errorFn);
+ },
+ // private
+ addAddressToMap : function(response, status, addr, marker, clear, center, listeners){
+ if (!response || status !== 'OK') {
+ this.respErrorMsg(status);
+ }else{
+ var place = response[0].geometry.location,
+ accuracy = this.getLocationTypeInfo(response[0].geometry.location_type,'level'),
+ reqAccuracy = this.getLocationTypeInfo(this.minGeoAccuracy,'level');
+ if (accuracy === 0) {
+ this.geoErrorMsg(this.geoErrorTitle, this.geoErrorMsgUnable);
+ }else{
+ if (accuracy < reqAccuracy) {
+ this.geoErrorMsg(this.geoErrorTitle, Ext.String.format(this.geoErrorMsgAccuracy, response[0].geometry.location_type, this.getLocationTypeInfo(response[0].geometry.location_type,'msg')));
+ }else{
+ point = new google.maps.LatLng(place.lat(),place.lng());
+ if (center){
+ this.getMap().setCenter(point, this.zoomLevel);
+ this.lastCenter = point;
+ }
+ if (typeof marker === 'object') {
+ if (!marker.title){
+ marker.title = response.formatted_address;
+ }
+ var mkr = this.addMarker(point, marker, clear, false, listeners);
+ if (marker.callback){
+ marker.callback.call(this, mkr, point);
+ }
+ }
+ }
+ }
+ }
+
+ },
+ // private
+ geoErrorMsg : function(title,msg){
+ if (this.displayGeoErrors) {
+ Ext.MessageBox.alert(title,msg);
+ }
+ },
+ // private
+ respErrorMsg : function(code){
+ Ext.each(this.respErrors, function(obj){
+ if (code == obj.code){
+ Ext.MessageBox.alert(this.respErrorTitle, obj.msg);
+ }
+ }, this);
+ },
+ // private
+ getLocationTypeInfo: function(location_type,property){
+ var val = 0;
+ Ext.each(this.locationTypes, function(itm){
+ if (itm.code === location_type){
+ val = itm[property];
+ }
+ });
+ return val;
+ }
+});
+</script>
<script type="text/javascript">
Ext.onReady(function() {
@@ -22,7 +761,7 @@ Ext.onReady(function() {
Ext.define('Position', {
extend: 'Ext.data.Model',
fields: [
- {name: 'device_id', type: 'int'},
+ {name: 'device_id', type: 'int'},
{name: 'time', type: 'date'},
{name: 'valid', type: 'boolean'},
{name: 'latitude', type: 'float'},
@@ -40,9 +779,15 @@ Ext.onReady(function() {
id: 'devices',
model: 'Device',
fields: ['id', 'imei'],
+ autoSync: true,
proxy: {
type: 'ajax',
- url: devicesUrl,
+ api: {
+ create: devicesUrl + '?action=create',
+ read: devicesUrl,
+ update: devicesUrl + '?action=update',
+ destroy: devicesUrl + '?action=destroy'
+ },
reader: {
type: 'json',
root: 'results'
@@ -73,7 +818,10 @@ Ext.onReady(function() {
}
});
- console.log(positions);
+ var map = Ext.create('Ext.ux.GMapPanel', {
+ id: 'gmap',
+ setCenter: {lat: 0, lng: 0}
+ });
var devicesPanel = Ext.create('Ext.grid.Panel', {
title: 'Devices',
@@ -98,7 +846,10 @@ Ext.onReady(function() {
id: 'device_add',
text: 'Add',
handler : function() {
- Ext.Msg.prompt('Add', 'Device IMEI:', function() {
+ Ext.Msg.prompt('Add', 'Device IMEI:', function(btn, text) {
+ if (btn == 'ok') {
+ devices.add({imei: text});
+ }
});
}
},
@@ -107,7 +858,10 @@ Ext.onReady(function() {
text: 'Remove',
disabled: true,
handler : function() {
- Ext.Msg.confirm('Remove', 'Are you sure to remove item?', function() {
+ Ext.Msg.confirm('Remove', 'Are you sure to remove item?', function(btn) {
+ if (btn == 'yes') {
+ devices.remove(devicesPanel.getSelectionModel().getLastSelected());
+ }
});
}
},
@@ -116,7 +870,10 @@ Ext.onReady(function() {
text: 'Edit',
disabled: true,
handler : function() {
- Ext.Msg.prompt('Edit', 'Device IMEI:', function() {
+ Ext.Msg.prompt('Edit', 'Device IMEI:', function(btn, text) {
+ if (btn == 'ok') {
+ devicesPanel.getSelectionModel().getLastSelected().set('imei', text);
+ }
}, this, false, devicesPanel.getSelectionModel().getLastSelected().get('imei'));
}
}
@@ -182,21 +939,28 @@ Ext.onReady(function() {
{header: 'Speed', dataIndex: 'speed'},
{header: 'Course', dataIndex: 'course'},
{header: 'Power', dataIndex: 'power'}
- ]
+ ],
+ listeners: {
+ selectionchange: function(sender, selected, eOpts) {
+ if (selected.length != 0) {
+ var lat = positionsPanel.getSelectionModel().getLastSelected().get('latitude');
+ var lng = positionsPanel.getSelectionModel().getLastSelected().get('longitude');
+ var point = new google.maps.LatLng(lat, lng);
+ map.addMarker(point, {lat: lat, lng: lng}, true, true);
+ } else {
+ map.clearMarkers(); // private?
+ }
+ }
+ }
});
- /*var map = Ext.create('Ext.ux.GMapPanel', {
- id: 'gmap',
- setCenter: {lat: 0, lng: 0}
- });*/
-
var mapPanel = Ext.create('Ext.panel.Panel', {
title: 'Map',
region: 'center',
- margins: {top: 5, bottom: 0, right: 5, left: 0}/*,
+ margins: {top: 5, bottom: 0, right: 5, left: 0},
layout: 'fit',
- items: [map]*/
+ items: [map]
});
Ext.create('Ext.container.Viewport', {
diff --git a/src/org/traccar/Server.java b/src/org/traccar/Server.java
index fda5e6ad3..654d32321 100644
--- a/src/org/traccar/Server.java
+++ b/src/org/traccar/Server.java
@@ -86,15 +86,19 @@ public class Server {
initAvl08Server(properties);
// Initialize web server
- Integer port = Integer.valueOf(properties.getProperty("http.port", "8082"));
- webServer = new WebServer(port, dataManager);
+ if (Boolean.valueOf(properties.getProperty("http.enable"))) {
+ Integer port = Integer.valueOf(properties.getProperty("http.port", "8082"));
+ webServer = new WebServer(port, dataManager);
+ }
}
/**
* Start
*/
public void start() {
- webServer.start();
+ if (webServer != null) {
+ webServer.start();
+ }
for (Object server: serverList) {
((TrackerServer) server).start();
}
@@ -107,7 +111,9 @@ public class Server {
for (Object server: serverList) {
((TrackerServer) server).stop();
}
- webServer.stop();
+ if (webServer != null) {
+ webServer.stop();
+ }
}
/**
diff --git a/src/org/traccar/helper/NamedParameterStatement.java b/src/org/traccar/helper/NamedParameterStatement.java
index c86217182..e898af2d3 100644
--- a/src/org/traccar/helper/NamedParameterStatement.java
+++ b/src/org/traccar/helper/NamedParameterStatement.java
@@ -30,12 +30,12 @@ public class NamedParameterStatement {
* Index mapping
*/
private final Map indexMap;
-
+
/**
* Query string
*/
private final String parsedQuery;
-
+
/**
* Database connection
*/
@@ -106,7 +106,7 @@ public class NamedParameterStatement {
return parsedQuery.toString();
}
-
+
public void prepare() throws SQLException {
try {
if (statement == null) {
@@ -134,6 +134,13 @@ public class NamedParameterStatement {
}
/**
+ * Return generated keys
+ */
+ public ResultSet getGeneratedKeys() throws SQLException {
+ return statement.getGeneratedKeys();
+ }
+
+ /**
* Immediately closes the statement
*/
public void close() throws SQLException {
diff --git a/src/org/traccar/http/WebServer.java b/src/org/traccar/http/WebServer.java
index d42f8fe2c..53cdb2966 100644
--- a/src/org/traccar/http/WebServer.java
+++ b/src/org/traccar/http/WebServer.java
@@ -20,6 +20,8 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Iterator;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -79,28 +81,65 @@ public class WebServer {
out.flush();
}
+ private Device parseDevice(String json) {
+
+ Pattern pattern = Pattern.compile("\\{\"id\":(\\d+),\"imei\":\"(.*)\"\\}");
+ Matcher parser = pattern.matcher(json);
+ if (parser.matches()) {
+ Device device = new Device();
+ device.setId(Long.valueOf(parser.group(1)));
+ device.setImei(parser.group(2));
+ return device;
+ }
+
+ return null;
+ }
+
+ // TODO: separate method into small parts
public void handleData(String target,Request baseRequest,HttpServletRequest request,HttpServletResponse response)
throws IOException, ServletException {
response.setContentType("application/json");
PrintWriter out = response.getWriter();
- out.print("{'success':true,'results':[");
try {
if (target.equals("/devices.json")) {
- Iterator<Device> i = dataManager.getDevices().iterator();
- while (i.hasNext()) {
- Device device = i.next();
+ String action = request.getParameter("action");
+ if (action == null) {
+ Iterator<Device> i = dataManager.getDevices().iterator();
+ out.print("{'success':true,'results':[");
+ while (i.hasNext()) {
+ Device device = i.next();
+ out.format("{'id':%d,'imei':'%s'}",
+ device.getId(),
+ device.getImei());
+ if (i.hasNext()) out.print(",");
+ }
+ } else if (action.equals("create")) {
+ Device device = parseDevice(request.getReader().readLine());
+ dataManager.addDevice(device);
+ out.print("{'success':true,'results':[");
+ out.format("{'id':%d,'imei':'%s'}",
+ device.getId(),
+ device.getImei());
+ } else if (action.equals("update")) {
+ Device device = parseDevice(request.getReader().readLine());
+ dataManager.updateDevice(device);
+ out.print("{'success':true,'results':[");
out.format("{'id':%d,'imei':'%s'}",
device.getId(),
device.getImei());
- if (i.hasNext()) out.print(",");
+ } else if (action.equals("destroy")) {
+ Device device = parseDevice(request.getReader().readLine());
+ dataManager.removeDevice(device);
+ out.print("{'success':true,'results':[");
}
} else if (target.equals("/positions.json")) {
+ out.print("{'success':true,'results':[");
String deviceId = request.getParameter("deviceId");
if (deviceId != null) {
Iterator<Position> i = dataManager.getPositions(Long.valueOf(deviceId)).iterator();
@@ -120,10 +159,10 @@ public class WebServer {
}
} catch (Exception error) {
+ out.print("{'success':false,'results':[");
System.out.println(error.getMessage());
}
- //" {'id': 1, 'imei': '123456789012345'} ]}");
out.print("]}");
out.flush();
}
@@ -138,7 +177,7 @@ public class WebServer {
{
if (target.equals("/") || target.equals("/index.html")) {
handleIndex(target, baseRequest, request, response);
- } else if (target.matches("/favicon.ico")) {
+ } else if (target.equals("/favicon.ico")) {
handleIcon(target, baseRequest, request, response);
} else if (target.matches("/.+\\.json")) {
handleData(target, baseRequest, request, response);
diff --git a/src/org/traccar/model/DataManager.java b/src/org/traccar/model/DataManager.java
index d0f290962..70cf63380 100644
--- a/src/org/traccar/model/DataManager.java
+++ b/src/org/traccar/model/DataManager.java
@@ -27,8 +27,8 @@ public interface DataManager {
*/
public List<Device> getDevices() throws Exception;
public void addDevice(Device device) throws Exception;
- public void addUpdate(Device device) throws Exception;
- public void addRemove(Device device) throws Exception;
+ public void updateDevice(Device device) throws Exception;
+ public void removeDevice(Device device) throws Exception;
public Device getDeviceByImei(String imei) throws Exception;
/**
diff --git a/src/org/traccar/model/DatabaseDataManager.java b/src/org/traccar/model/DatabaseDataManager.java
index 7d90ac194..c5492bc8a 100644
--- a/src/org/traccar/model/DatabaseDataManager.java
+++ b/src/org/traccar/model/DatabaseDataManager.java
@@ -35,16 +35,13 @@ public class DatabaseDataManager implements DataManager {
* Database statements
*/
private NamedParameterStatement queryGetDevices;
+ private NamedParameterStatement queryAddDevice;
+ private NamedParameterStatement queryUpdateDevice;
+ private NamedParameterStatement queryRemoveDevice;
+ private NamedParameterStatement queryGetPositions;
private NamedParameterStatement queryAddPosition;
/**
- * Devices cache
- */
- private Map devices;
- private Calendar devicesLastUpdate;
- private Long devicesRefreshDelay;
-
- /**
* Initialize database
*/
private void initDatabase(Properties properties)
@@ -70,9 +67,24 @@ public class DatabaseDataManager implements DataManager {
String password = properties.getProperty("database.password");
AdvancedConnection connection = new AdvancedConnection(url, user, password);
- String query = properties.getProperty("database.selectDevice");
+ // Load statements from configuration
+ String query;
+
+ query = properties.getProperty("database.selectDevice");
queryGetDevices = new NamedParameterStatement(connection, query);
+ query = properties.getProperty("database.insertDevice");
+ queryAddDevice = new NamedParameterStatement(connection, query);
+
+ query = properties.getProperty("database.updateDevice");
+ queryUpdateDevice = new NamedParameterStatement(connection, query);
+
+ query = properties.getProperty("database.deleteDevice");
+ queryRemoveDevice = new NamedParameterStatement(connection, query);
+
+ query = properties.getProperty("database.selectPosition");
+ queryGetPositions = new NamedParameterStatement(connection, query);
+
query = properties.getProperty("database.insertPosition");
queryAddPosition = new NamedParameterStatement(connection, query);
}
@@ -93,9 +105,46 @@ public class DatabaseDataManager implements DataManager {
return deviceList;
}
- public void addDevice(Device device) {} // TODO: implement
- public void addUpdate(Device device) {} // TODO: implement
- public void addRemove(Device device) {} // TODO: implement
+ public synchronized void addDevice(Device device) throws SQLException {
+
+ queryAddDevice.prepare();
+ queryAddDevice.setString("imei", device.getImei());
+ queryAddDevice.executeUpdate();
+
+ // Find generated id
+ ResultSet result = queryAddDevice.getGeneratedKeys();
+ if (result.next()) {
+ device.setId(result.getLong(1));
+ }
+
+ devices = null;
+ }
+
+ public synchronized void updateDevice(Device device) throws SQLException {
+
+ queryUpdateDevice.prepare();
+ queryUpdateDevice.setLong("id", device.getId());
+ queryUpdateDevice.setString("imei", device.getImei());
+ queryUpdateDevice.executeUpdate();
+
+ devices = null;
+ }
+
+ public synchronized void removeDevice(Device device) throws SQLException {
+
+ queryRemoveDevice.prepare();
+ queryRemoveDevice.setLong("id", device.getId());
+ queryRemoveDevice.executeUpdate();
+
+ devices = null;
+ }
+
+ /**
+ * Devices cache
+ */
+ private Map devices;
+ private Calendar devicesLastUpdate;
+ private Long devicesRefreshDelay;
public Device getDeviceByImei(String imei) throws SQLException {
@@ -110,20 +159,26 @@ public class DatabaseDataManager implements DataManager {
return (Device) devices.get(imei);
}
- public List<Position> getPositions(Long deviceId) { // TODO: implement
+ public synchronized List<Position> getPositions(Long deviceId) throws SQLException {
List<Position> positionList = new LinkedList();
- Position p = new Position();
- p.setDeviceId(new Long(1));
- p.setTime(new Date());
- p.setValid(true);
- p.setLatitude(1.0);
- p.setLongitude(1.0);
- p.setSpeed(1.0);
- p.setCourse(1.0);
-
- positionList.add(p);
+ queryGetPositions.prepare();
+ queryGetPositions.setLong("device_id", deviceId);
+ ResultSet result = queryGetPositions.executeQuery();
+ while (result.next()) {
+ // TODO: include other parameters
+ Position position = new Position();
+ position.setDeviceId(result.getLong("device_id"));
+ position.setTime(result.getTimestamp("time"));
+ position.setValid(result.getBoolean("valid"));
+ position.setLatitude(result.getDouble("latitude"));
+ position.setLongitude(result.getDouble("longitude"));
+ position.setSpeed(result.getDouble("speed"));
+ position.setCourse(result.getDouble("course"));
+ position.setPower(result.getDouble("power"));
+ positionList.add(position);
+ }
return positionList;
}
@@ -144,6 +199,8 @@ public class DatabaseDataManager implements DataManager {
queryAddPosition.setString("extended_info", position.getExtendedInfo());
queryAddPosition.executeUpdate();
+
+ // TODO: probably return row id
}
}