'+
+ ' '+
+ ' '+
+ '
=0){
+ return ' ';
+ }
+ if(definedSqlTypes[i]["code"]=="html")
+ return '';
+ if(definedSqlTypes[i]["code"]=="text")
+ return '';
+ if(definedSqlTypes[i]["code"]=="boolean")
+ return ' ';
+ if(definedSqlTypes[i]["code"]=="date" || definedSqlTypes[i]["code"]=="datetime")
+ return ' ';
+ if(definedSqlTypes[i]["code"]=="float")
+ return ' ';
+ return definedSqlTypes[i]["code"];
+ }
+ }
+ return null;
+}
+
+function printOptionalCheckbox(obj,cid){
+ if(definedSqlTypes.length==0){
+ definedSqlTypes=JSON.parse(window.Android.displayTable("select id,code from mm4me_ftypes where ftype='e' order by name",[]));
+ }
+
+ var res=' ';
+ for(var i in definedSqlTypes){
+ if(definedSqlTypes[i]["id"]==obj["ftype"]){
+ if(definedSqlTypes[i]["code"]=="bytea"){
+ var tmpStr="";
+ return res;
+ }
+ else if(definedSqlTypes[i]["code"]=="geometry"){
+ return res
+ }
+ }
+ }
+ return '';
+}
+
+var refTypeId=null;
+var editPrintedOnce=[];
+/*****************************************************************************
+ * Create HTML part to display the line containing both the title and the
+ * corresponding input for a given table's field.
+ *****************************************************************************/
+function printEditionFields(obj,myRoot,cid,mid){
+ var list1=window.Android.displayTable("select * from mm4me_edition_fields where mm4me_edition_fields.edition>0 and eid="+obj["id"]+" order by mm4me_edition_fields.id asc",[]);
+ if(!editSchema[mid])
+ editSchema[mid]={};
+ editSchema[mid][obj["id"]]=JSON.parse(list1);
+ list1=JSON.parse(list1);
+ myRoot.find(".tab-content").first().append(''+obj["description"]+'
');
+ for(var j in list1)
+ if(list1[j]["edition"]>0) {
+ myRoot.find(".tab-content").first().children().last().append(
+ '');
+ if(list1[j]["dependencies"]){
+ try{
+ editPrintedOnce.push(list1[j]["name"]);
+ console.log("JSON PARSE")
+ console.log(list1[j]["dependencies"]);
+ var objJson=JSON.parse(list1[j]["dependencies"]);
+ console.log("JSON PARSE OK");
+ if(!refTypeId)
+ refTypeId=JSON.parse(window.Android.displayTable("select id from mm4me_ftypes where ftype='e' and code='ref'",[]))[0]["id"];
+ console.log(refTypeId);
+ for(i in objJson){
+ if(objJson[i]["myself"]){
+ console.log("IS MYSELF!!");
+ for(k in objJson[i]["myself"]){
+ for(l in objJson[i]["myself"][k]){
+
+
+ if(objJson[i]["myself"][k][l]["dependents"]){
+ for(m in objJson[i]["myself"][k][l]["dependents"]){
+ for(n in objJson[i]["myself"][k][l]["dependents"][m]){
+ console.log(objJson[i]["myself"][k][l]["dependents"][m][n]["sql_query"]);
+ console.log(objJson[i]["myself"][k][l]["dependents"][m][n]["label"]);
+ var lObj={"id": n, "ftype":refTypeId,"value":objJson[i]["myself"][k][l]["dependents"][m][n]["sql_query"]};
+ //if(!myRoot.find('select[name="field_'+list1[j]["id"]+'"]').parent().find('select[name="field_'+n+'"]').length)
+ myRoot.find('select[name="field_'+list1[j]["id"]+'"]').last().parent().prepend(
+ '');
+ console.log(myRoot.find('select[name="field_'+n+'"]'));
+ console.log(printCurrentType(lObj,mid));
+ (function(a,b){
+ myRoot.find('select[name="field_'+n+'"]').off('change');
+ myRoot.find('select[name="field_'+n+'"]').on('change',function(){
+ console.log('select[name="field_'+n+'"]');
+ var req=cleanupTableName(a["value"]);
+ if(a["value"].indexOf("WHERE")<0){
+ req=req.replace(/order by/g,"where "+b["tfieldf"]+" "+b["operator"]+" "+(b["operator"]=="like"?"'":"")+$(this).val()+(b["operator"]=="like"?"'":"")+" order by")
+ }
+ console.log(req);
+ var res=JSON.parse(window.Android.displayTable(req,[]));
+ myRoot.find('select[name="field_'+a["id"]+'"]').html("");
+ for(ij in res){
+ var tmpStr=' ';
+ else{
+ tmpStr+=res[ij][kl]+'';
+ myRoot.find('select[name="field_'+a["id"]+'"]').append(tmpStr);
+ }
+ poi+=1;
+ }
+ }
+
+ })
+ })(list1[j],objJson[i]["myself"][k][l]["dependents"][m][n]);
+
+ }
+ }
+ }
+
+ console.log(objJson[i]["myself"][k][l]["sql_query"]);
+ console.log(objJson[i]["myself"][k][l]["label"]);
+ var lObj={"id": l,"ftype":refTypeId,"value":objJson[i]["myself"][k][l]["sql_query"]};
+ //if(!myRoot.find('select[name="field_'+list1[j]["id"]+'"]').parent().find('select[name="field_'+l+'"]').length)
+ myRoot.find('select[name="field_'+list1[j]["id"]+'"]').last().parent().prepend(
+ '');
+ console.log(myRoot.find('select[name="field_'+l+'"]'));
+ console.log('select[name="field_'+l+'"]');
+ (function(a,b,c,d,e){
+ myRoot.find('select[name="field_'+a+'"]').on('change',function(){
+ console.log('select[name="field_'+a+'"]');
+ if(b["dependents"]){
+ for(m in b["dependents"]){
+ for(n in b["dependents"][m]){
+ var req=cleanupTableName(b["dependents"][m][n]["sql_query"]);
+ if(req.indexOf("WHERE")<0)
+ req=req.replace(/order by/g," WHERE "+b["tfield"]+" "+b["operator"]+" "+(b["operator"]=="like"?"'":"")+$(this).val()+(b["operator"]=="like"?"'":"")+" order by ");
+ var res=JSON.parse(window.Android.displayTable(req,[]));
+ myRoot.find('select[name="field_'+n+'"]').html("");
+ for(ij in res){
+ var tmpStr=' ';
+ else{
+ tmpStr+=res[ij][kl]+'';
+ myRoot.find('select[name="field_'+n+'"]').append(tmpStr);
+ }
+ poi+=1;
+ }
+ }
+ myRoot.find('select[name="field_'+n+'"]').change();
+ }
+ }
+ }else{
+ var req=cleanupTableName(c["value"]);
+ var clause=b["tfield"]+" "+b["operator"]+" "+(b["operator"]=="like"?"'":"")+$(this).val()+(b["operator"]=="like"?"'":"");
+ if(d.length>1){
+ for(var i=0;i';
+ else{
+ tmpStr+=res1[ij1][kl1]+'';
+ myRoot.find('select[name="field_'+c["id"]+'"]').append(tmpStr);
+ }
+ poi+=1;
+ }
+ }
+ if(b["html_template"]){
+ myRoot.find('select[name="field_'+c["id"]+'"]').off('change');
+ myRoot.find('select[name="field_'+c["id"]+'"]').on('change',function(){
+ if(!$(this).parent().find(".html_layout"))
+ $(this).parent().append('
');
+
+ });
+ }
+ }catch(e){console.log(e);}
+ console.log(req);
+ }
+ });
+ })(l,objJson[i]["myself"][k][l],list1[j],objJson[i]["myself"],k);
+
+ //if(objJson[i]["myself"][k][l]["dependents"])
+ myRoot.find('select[name="field_'+l+'"]').change();
+
+ }
+ }
+ }else
+ console.log("Basic dependencies!");
+ }
+ }catch(e){
+ console.log(e);
+ }
+ }
+
+
+ }
+
+ myRoot.find(".tab-content").first().children().last().append(
+ ''+
+ ''+window.Android.translate((cid.indexOf('_')<0?'save':'add'))+' '+
+ '
');
+ console.log(JSON.stringify(currentTypes));
+ for(i in currentTypes){
+ console.log(currentTypes[i]);
+ $("#edition_form_"+cid).find("#"+currentTypes[i]).change();
+ }
+ myRoot.show();
+}
+
+/*****************************************************************************
+ * Execute an Insert SQL query for a given table
+ *****************************************************************************/
+function runInsertQuery(obj,mid,func){
+ if(MM4ME_DEBUG)
+ console.log($(obj).attr('id')+" "+mid);
+ var query="INSERT INTO "+cleanupTableName(allTables[mid].name);
+ var queryAttr=[];
+ var queryValues0=[];
+ var queryValues=[];
+ var queryTypes=[];
+ $(obj).find("input,select,textarea").each(function(){
+ if(MM4ME_DEBUG)
+ console.log($(this).attr("name")+" <> "+$(this).val());
+ try{
+
+ var cid=$(this).attr("name").replace(/field_/g,"");
+ console.log(!$("#"+cid+"_display").length || ($("#"+cid+"_display").length && $("#"+cid+"_display").is(":checked")));
+ var found=false;
+ if((!$("#"+cid+"_display").length || ($("#"+cid+"_display").length && $("#"+cid+"_display").is(":checked"))))
+ for(var i in editSchema[mid]){
+ for(var j in editSchema[mid][i]){
+ if(editSchema[mid][i][j]["id"]==cid){
+ if(editSchema[mid][i][j]["name"].indexOf("unamed")<0 && $(this).parent().is(":visible")){
+ if(MM4ME_DEBUG)
+ console.log(editSchema[mid][i][j]["name"]+" <> "+$(this).val());
+ queryAttr.push(editSchema[mid][i][j]["name"].replace(/wkb_geometry/g,"geometry"));
+ queryValues.push($(this).val());
+ queryValues0.push("?");
+ queryTypes.push(parseInt(editSchema[mid][i][j]["ftype"]));
+ }
+ found=true;
+ break;
+ }
+ }
+ if(found)
+ break;
+ }
+ }catch(e){
+ console.log(e);
+ }
+ });
+ var regs=[
+ new RegExp("\\[","g"),
+ new RegExp("\\]","g")
+ ];
+
+ for(var i in editSchema[mid]){
+ for(var j in editSchema[mid][i]){
+ if(editSchema[mid][i][j]["ftype"]==EDITION_TYPE_FILE && $("#"+editSchema[mid][i][j]["id"]+"_display").length && $("#"+editSchema[mid][i][j]["id"]+"_display").is(":checked") ){
+ queryAttr.push(editSchema[mid][i][j]["name"]);
+ queryValues0.push("?");
+ queryTypes.push(parseInt(editSchema[mid][i][j]["ftype"]));
+ queryValues.push($(obj).find("#value_"+editSchema[mid][i][j]["id"]).find("img").attr("src"));
+ //subquery+=",readfile('"+$(obj).find("#value_"+editSchema[mid][i][j]["id"]).find("img").attr("src")+"')";
+ }
+ }
+ }
+
+ var ccol=getPKey(cleanupTableName(allTables[mid].name));
+ queryAttr.push(ccol);
+ var osubquery=("(select max("+ccol+") from "+cleanupTableName(allTables[mid].name)+")");
+ var subquery=("(select CASE WHEN count(*) > 0 THEN max("+ccol+")+1 ELSE 1 END from "+cleanupTableName(allTables[mid].name)+")");
+ if(refTables[mid]){
+ queryAttr.push(refTables[mid]["col"]);
+ subquery+=","+referenceIds[mtable];
+ }
+
+ //var req=(query+" ("+queryAttr.join(",")+") VALUES "+JSON.stringify(queryValues,null).replace(regs[0],"(")+"");
+ var req=(query+" ("+queryAttr.join(",")+") VALUES ("+queryValues0.join(",")+","+subquery+")");
+ if(MM4ME_DEBUG){
+ console.log(req);
+ console.log(queryTypes);
+ }
+ var res=window.Android.executeQuery(req,queryValues,queryTypes);
+ window.Android.executeQuery("INSERT INTO history_log (tbl,sql,pkey_value) VALUES (?,?,"+osubquery+")",[cleanupTableName(allTables[mid].name),req],[1,1]);
+ try{
+ window.Android.showToast(window.Android.translate("insert_success"));
+ func(mid);
+ }catch(e){
+ window.Android.notify("Error: "+e);
+ }
+
+}
+
+var systemSelectedIndex=-1;
+/*****************************************************************************
+ * Execute an Update SQL query for a given table
+ *****************************************************************************/
+function runUpdateQuery(obj,mid,func){
+ var query="UPDATE "+cleanupTableName(allTables[mid].name)+" set ";
+ var queryAttr=[];
+ var queryValues0=[];
+ var queryValues=[];
+ var queryTypes=[];
+ var lastValue=$("#exampleTable"+((mid==mtable)?"":"_"+mid)).find(".selected").find('input[type=hidden]').first().val()?$("#exampleTable"+((mid==mtable)?"":"_"+mid)).find(".selected").find('input[type=hidden]').first().val():systemSelectedIndex;
+ var ccol=getPKey(cleanupTableName(allTables[mid].name));
+ var queryEnd=" WHERE "+ccol+"=?";
+ var lcnt=0;
+ $(obj).find("input,select,textarea").each(function(){
+ if(MM4ME_DEBUG)
+ console.log($(this).attr("name")+" <> "+$(this).val());
+ try{
+ var cid=$(this).attr("name").replace(/field_/g,"");
+ console.log($(this).parent().is(":visible"));
+ var found=false;
+ if($(this).parent().is(":visible") || $(this).is(":visible"))
+ for(var i in editSchema[mid]){
+ for(var j in editSchema[mid][i]){
+ if(editSchema[mid][i][j]["id"]==cid){
+ if(editSchema[mid][i][j]["name"].indexOf("unamed")<0){
+ //console.log(JSON.stringify(editSchema[mid][i][j]));
+ if(MM4ME_DEBUG)
+ console.log(editSchema[mid][i][j]["name"]+" <> "+$(this).val());
+ query+=(lcnt>0?", ":"")+editSchema[mid][i][j]["name"].replace(/wkb_geometry/g,"geometry")+"=?";
+ queryTypes.push(parseInt(editSchema[mid][i][j]["ftype"]));
+ queryValues.push($(this).val());
+ //query+=(lcnt>0?", ":"")+editSchema[mid][i][j]["name"]+"="+JSON.stringify($(this).val(),null);
+ //queryAttr.push(editSchema[i][j]["name"]);
+ //queryValues.push($(this).val());
+ lcnt+=1;
+ }
+ found=true;
+ break;
+ }
+ }
+ if(found)
+ break;
+ }
+ }catch(e){
+ console.log(e);
+ }
+ });
+ for(var i in editSchema[mid]){
+ for(var j in editSchema[mid][i]){
+ if(editSchema[mid][i][j]["ftype"]==EDITION_TYPE_FILE && $(obj).find("#value_"+editSchema[mid][i][j]["id"]).find("img").length && $("#"+editSchema[mid][i][j]["id"]+"_display").length && $("#"+editSchema[mid][i][j]["id"]+"_display").is(":checked") ){
+ //queryAttr.push(editSchema[mid][i][j]["name"]);
+ query+=(lcnt>0?", ":"")+editSchema[mid][i][j]["name"]+"=?";
+ queryTypes.push(parseInt(editSchema[mid][i][j]["ftype"]));
+ queryValues.push($(obj).find("#value_"+editSchema[mid][i][j]["id"]).find("img").attr("src"));
+ //subquery+=",readfile('"+$(obj).find("#value_"+editSchema[mid][i][j]["id"]).find("img").attr("src")+"')";
+ lcnt+=1;
+ }
+ }
+ }
+
+ queryValues.push(lastValue);
+ queryTypes.push(lastValue,1);
+ var req=query+queryEnd;
+ if(MM4ME_DEBUG)
+ console.log(req);
+ if(window.Android.executeQuery(req,queryValues,queryTypes)>=0){
+ window.Android.executeQuery("INSERT INTO history_log (tbl,sql,pkey_value) VALUES (?,?,?)",[cleanupTableName(allTables[mid].name),req,lastValue],[1,1,1]);
+ window.Android.showToast(window.Android.translate("update_success"));
+ func(mid);
+ }
+}
+
+/*****************************************************************************
+ * Get the table's primary key, stored in the primary_keys table
+ *****************************************************************************/
+function getPKey(tbl){
+ var req0="SELECT col FROM primary_keys where tbl='"+tbl+"'";
+ if(MM4ME_DEBUG)
+ console.log(req0);
+ var list01=JSON.parse(window.Android.displayTable(req0,[]));
+ if(MM4ME_DEBUG)
+ console.log(JSON.stringify(list01[0]));
+ return list01[0]["col"];
+}
+
+/*****************************************************************************
+ * Get the geometry type, stored in the mm4me_gc table
+ *****************************************************************************/
+function getGeometryType(tbl){
+ var req0="select type from mm4me_gc where f_table_schema||'_'||f_table_name = "+tbl+"";
+ if(MM4ME_DEBUG)
+ console.log(req0);
+ var list01=JSON.parse(window.Android.displayTable(req0,[]));
+ if(MM4ME_DEBUG)
+ console.log(JSON.stringify(list01[0]));
+ return list01[0]["type"];
+}
+
+/*****************************************************************************
+ * The fucntion to call at the end of insert or update query
+ *****************************************************************************/
+function editTableReact(tid){
+ var mid=tid;
+ if(MM4ME_DEBUG)
+ console.log("editTableReact("+mid+')');
+ if(mid==mtable){
+ $('.mm4me_listing').find('ul').first().find('a').first().click();
+ $(".require-select").hide();
+ listTable(allTables[mid].id,tblName,tblTitle,false);
+ }
+ else{
+ $("#sub_tableContent_"+mid).find(".require-select").hide();
+ $("#sub_tableContent_"+mid).find('ul').first().find('a').first().click();
+ listInnerTable(mid,refTables[mid]["vid"],allTables[mid]["name"],refTables[mid]["name"],true,"_"+refTables[mid]["vid"],refTables[mid]["col"]+"="+referenceIds[refTables[mid]["oid"]]);
+ }
+}
+
+/*****************************************************************************
+ * Display the content of the current edited table.
+ *****************************************************************************/
+function listTable(id,name,title,init,prefix){
+ tblId=mainTable[id];
+ tblName=name;
+ tblTitle=title;
+
+ var list=window.Android.displayTable("select mm4me_editions.id,mm4me_editions.name,mm4me_editions.description from mm4me_editions,mm4me_tables where mm4me_editions.ptid=mm4me_tables.id and mm4me_tables.id="+tblId+" and step>=0 order by mm4me_editions.step asc",[]);
+ if(MM4ME_DEBUG)
+ console.log(list);
+ list=JSON.parse(list);
+ if(MM4ME_DEBUG)
+ console.log((!allTables[tblId]));
+ if(!allTables[tblId]){
+ allTables[tblId]={"id":id,"name":name,"title":title};
+ mtable=tblId;
+
+ $(".mm4me_edition").find("ul").first().html("");
+ $(".mm4me_edition").find(".well").first().html("");
+ $(".mm4me_listing").find("ul").first().append(''+window.Android.translate('table')+' ');
+ var cnt=0;
+ for(var i in list){
+ lastEdition[list[i]["id"]]=list[i];
+ $(".mm4me_edition").find("ul").first().append(''+list[i]["name"]+' ');
+ printEditionFields(list[i],$("#edition_form_edit"),list[i]["id"],mainTable[id]);
+ if(cnt==0){
+ try{
+ var cid=list[i]["id"]+"_0";
+ $(".mm4me_listing").find("ul").first().append(''+window.Android.translate('edit')+' ');
+ $(".mm4me_listing").find("ul").first().append(''+window.Android.translate('add')+' ');
+ $(".require-select").hide();
+ printEditionFields(list[i],$(".mm4me_listing"),cid,mainTable[id]);
+ }catch(e){
+ console.log("**** ERROR ***> "+e);
+ }
+ }
+ cnt+=1;
+ }
+ }
+
+ $('.mm4me_listing').find('ul').first().find('a').click(function (e) {
+ e.preventDefault();
+ if($(this).parent().hasClass('require-select'))
+ $('.mm4me_edition').show();
+ else
+ $('.mm4me_edition').hide();
+ if(MM4ME_DEBUG)
+ console.log("DEBUG !! "+$(this).hasClass('require-select'))
+ $(this).tab('show');
+ })
+ $('.mm4me_edition').find('ul').find('a').click(function (e) {
+ e.preventDefault();
+ $(this).tab('show');
+ })
+ $('.mm4me_listing').find('ul').first().find('a').first().click();
+ $('.mm4me_edition').find('ul').find('a').first().click();
+ $('.swagEditor').summernote();
+ $(".mm-act-add").click(function(){
+ runInsertQuery($(this).parent().parent(),mainTable[id],editTableReact);
+ });
+ $(".mm-act-save").click(function(){
+ runUpdateQuery($(this).parent().parent(),mainTable[id],editTableReact);
+ });
+ $(".breadcrumb").children().last().remove();
+ //$('.mm4me_edition').hide();
+
+ var list=window.Android.displayTable("SELECT id,name,value,alias,width,class from mm4me_view_fields where vid="+id+" order by id asc",[]);
+ if(MM4ME_DEBUG)
+ console.log(list);
+ list=JSON.parse(list);
+ var columnNames=[];
+ var columns=[];
+ var sqlColumns="";
+ var orderColumn="id";
+ var orderType="asc";
+ //alert(list);
+ for(var i=0;i0){
+ orderColumn=list[i]["name"];
+ if(list[i]["class"]==1)
+ orderType="desc";
+ }
+ }
+ var ccol=getPKey(cleanupTableName(name));
+ var req="SELECT "+ccol+" as ogc_fid FROM "+cleanupTableName(name)+" ORDER BY "+cleanupTableName(name)+"."+orderColumn+" "+orderType;
+ var list1=JSON.parse(window.Android.displayTable(req,[]));
+ req="SELECT "+sqlColumns+" FROM "+cleanupTableName(name)+" ORDER BY "+cleanupTableName(name)+"."+orderColumn+" "+orderType;
+ var list=JSON.parse(window.Android.displayTable(req,[]));
+ var dataSet = [];
+ if(list)
+ for(var i=0;i ';
+ }
+ if(MM4ME_DEBUG){
+ console.log(JSON.stringify(columnNames));
+ console.log(JSON.stringify(tmpData));
+ }
+ dataSet.push(tmpData);
+ }
+ var selected = [];
+
+ var localName="exampleTable"+(!prefix?"":prefix);
+ ((function(localName,mid){
+ if(init){
+ $(".breadcrumb").append(' '+window.Android.translate('view')+' ');
+ $(".breadcrumb").append(' '+tblTitle+' ');
+
+ var options={
+ data: dataSet,
+ columns: columns,
+ "scrollX": true,
+ scrollY: '50vh',
+ scrollCollapse: true,
+ select: true
+ };
+ if(langUrl!=null)
+ options["language"]={
+ url: langUrl
+ };
+ $('#'+localName).DataTable( options );
+ $('#'+localName+' tbody').on('click', 'tr', function () {
+ var id = this.id;
+ var index = $.inArray(id, selected);
+
+ if ( index === -1 ) {
+ selected.push( id );
+ } else {
+ selected.splice( index, 1 );
+ }
+ displayEditForm(mid,$(this).find("input[name=id]").first().val(),true);
+ var tmp=$(this).hasClass('selected');
+ var closure=$(this);
+ $('#'+localName+' tbody tr').each(function(){$(this).removeClass('selected')});
+ $(this).toggleClass('selected');
+ if(!tmp)
+ $(this).toggleClass('selected');
+ /*else
+ $(this).removeClass('selected');*/
+
+ } );
+ }else{
+ $('#'+localName).dataTable().fnClearTable();
+ $('#'+localName).dataTable().fnAddData(dataSet);
+ }
+ })(localName,mainTable[id]));
+
+ setTimeout(function() { updateChangingFields(changingFields) }, 1500);
+ $('.mm4me_listing').show();
+ $('.mm4me_content').hide();
+}
+
+/*****************************************************************************
+ * Update a field depending on another field value (i.e region > department)
+ *****************************************************************************/
+function updateChangingFields(changingFields){
+ if(MM4ME_DEBUG)
+ console.log(JSON.stringify(changingFields));
+ try{
+ for(var i=0;i';
+ else
+ cStr+=changingField[j][ckey]["values"][cIndex][i][lkey]+' ';
+ cnt+=1;
+ }
+ cnt0+=1;
+ $("select[name=field_"+changingField[j][ckey]["id"]+"]").append(cStr);
+ }
+ if(i==0)
+ $("select[name=field_"+changingField[j][ckey]["id"]+"]").html(''+window.Android.translate('none')+' ');
+ $("select[name=field_"+changingField[j][ckey]["id"]+"]").change();
+ }else{
+ console.log("DISPLAY ELEMENT IF CINDEX >=0 ");
+ //console.log(JSON.stringify(changingField[j][ckey]));
+ console.log('input[name="field_'+ckey+'"],select[name="field_'+ckey+'"],textarea[name="field_'+ckey+'"]');
+ console.log($('input[name="field_'+changingField[j][ckey]["id"]+'"],select[name="field_'+changingField[j][ckey]["id"]+'"],textarea[name="field_'+changingField[j][ckey]["id"]+'"]').parent().parent().html());
+ var mycKey=changingField[j][ckey]["id"];
+ if(cIndex<0)
+ $('input[name="field_'+mycKey+'"],select[name="field_'+mycKey+'"],textarea[name="field_'+mycKey+'"]').parent().parent().hide();
+ else
+ $('input[name="field_'+mycKey+'"],select[name="field_'+mycKey+'"],textarea[name="field_'+mycKey+'"]').parent().parent().show();
+ }
+ }
+ }
+ };
+ };
+ $("select[name=field_"+key+"]").off('change');
+ $("select[name=field_"+key+"]").change(localFunc(changingFields[i][key]["dep"]));
+ $("select[name=field_"+key+"]").change();
+ }
+ }
+ }catch(e){
+ console.log(e);
+ setTimeout(function() { updateChangingFields(changingFields) }, 500);
+ }
+}
+
+/*****************************************************************************
+ * Display the content of a table referencing the current edited table.
+ *****************************************************************************/
+function listInnerTable(id,vid,name,title,init,prefix,clause,ref){
+ var list=JSON.parse(window.Android.displayTable("select mm4me_tables.id as tid,mm4me_tables.name as tname,mm4me_editions.id,mm4me_editions.name from mm4me_editions,mm4me_tables where mm4me_editions.ptid=mm4me_tables.id and mm4me_tables.id="+id+" order by mm4me_editions.step asc",[]));
+ var cnt=0;
+ var detectInit=true;
+ var tid=0;
+ for(var i in list){
+ lastEdition[list[i]["id"]]=list[i];
+ tid=list[i]["tid"];
+ if(!allTables[list[i]["tid"]]){
+ allTables[list[i]["tid"]]={"id":list[i]["id"],"name":list[i]["tname"],"title":list[i]["name"]};
+ detectInit=false;
+ $("#sub_tableContent_"+id+"").find(".mm4me_edition").find("ul").first().append(''+list[i]["name"]+' ');
+ printEditionFields(list[i],$("#sub_tableContent_"+id+"").find(".mm4me_edition"),list[i]["id"],list[i]["tid"]);
+ if(cnt==0){
+ try{
+ var cid=list[i]["id"]+"_0";
+ $("#sub_tableContent_"+id+"").find("ul").first().append(''+window.Android.translate('table')+' ');
+ $("#sub_tableContent_"+id+"").find("ul").first().append(''+window.Android.translate('edit')+' ');
+ $("#sub_tableContent_"+id+"").find("ul").first().append(''+window.Android.translate('add')+' ');
+ $("#sub_tableContent_"+id+" .require-select").hide();
+ printEditionFields(list[i],$("#sub_tableContent_"+id+""),cid,list[i]["tid"]);
+ }catch(e){
+ console.log("**** ERROR ***> "+e);
+ }
+ }
+ var myRoot=$("#sub_tableContent_"+id+"");
+ myRoot.find('ul').first().find('a').click(function (e) {
+ e.preventDefault();
+ if($(this).parent().hasClass('require-select'))
+ myRoot.find('.mm4me_edition').show();
+ else
+ myRoot.find('.mm4me_edition').hide();
+ $(this).tab('show');
+ })
+ myRoot.find('ul').first().find('a').first().click();
+ myRoot.find('.mm4me_edition').find('ul').find('a').first().click();
+ myRoot.find('.swagEditor').summernote();
+ myRoot.find(".mm-act-add").click(function(){
+ runInsertQuery($(this).parent().parent(),tid,editTableReact);
+ });
+ myRoot.find(".mm-act-save").click(function(){
+ runUpdateQuery($(this).parent().parent(),tid,editTableReact);
+ });
+
+ }
+ cnt+=1;
+ }
+
+ var list=JSON.parse(window.Android.displayTable("SELECT id,name,value,alias,width,class from mm4me_view_fields where vid="+vid+" order by id",[]));
+ var columns=[];
+ var columnNames=[];
+ var sqlColumns="";
+ var orderColumn="id";
+ var orderType="asc";
+ for(var i=0;i0){
+ orderColumn=list[i]["name"];
+ if(list[i]["class"]==1)
+ orderType="desc";
+ }
+ }
+ var ccol=getPKey(cleanupTableName(name));
+ var req="SELECT "+ccol+" as ogc_fid FROM "+cleanupTableName(name)+" WHERE "+clause+" ORDER BY "+cleanupTableName(name)+"."+orderColumn+" "+orderType;
+ var list1=JSON.parse(window.Android.displayTable(req,[]));
+ req="SELECT "+sqlColumns+" FROM "+cleanupTableName(name)+" WHERE "+clause+" ORDER BY "+cleanupTableName(name)+"."+orderColumn+" "+orderType;
+ var list=JSON.parse(window.Android.displayTable(req,[]));
+ var dataSet = [];
+ for(var i=0;i ';
+ cnt+=1;
+ }
+ dataSet.push(tmpData);
+ }
+ var selected = [];
+
+ //$("#edition_form_table").html('');
+ var localName="exampleTable_"+id;
+ ((function(localName,sid){
+
+ if(!detectInit){
+
+ var options={
+ data: dataSet,
+ columns: columns,
+ "scrollX": true,
+ scrollY: '50vh',
+ scrollCollapse: true,
+ select: true,
+ "rowCallback": function( row, data ) {
+ if ( $.inArray(data.DT_RowId, selected) !== -1 ) {
+ $(row).addClass('selected');
+ }
+ }
+ };
+ if(langUrl!=null)
+ options["language"]={
+ url: langUrl
+ };
+
+ $('#'+localName).DataTable( options );
+ $('#'+localName+' tbody').on('click', 'tr', function () {
+ var id = this.id;
+ var index = $.inArray(id, selected);
+
+ if ( index === -1 ) {
+ selected.push( id );
+ } else {
+ selected.splice( index, 1 );
+ }
+ displayEditForm(sid,$(this).find("input[name=id]").first().val(),true);
+ var tmp=$(this).hasClass('selected');
+ $('#'+localName+' tbody tr').removeClass('selected');
+ $(this).toggleClass('selected');
+ if(!tmp)
+ $(this).toggleClass('selected');
+ } );
+
+ }else{
+ $('#'+localName).dataTable().fnClearTable();
+ if(dataSet.length>0)
+ $('#'+localName).dataTable().fnAddData(dataSet);
+ }
+ $("#sub_tableContent_"+id+"").find("ul").first().find('a').first().click();
+ })(localName,tid));
+
+}
+
+var onFormFirstLoad=null;
+/*****************************************************************************
+ * Show the edit form
+ *****************************************************************************/
+function displayEditForm(cid,selectedId,basic){
+ if(basic && !$("#exampleTable"+(cid==mtable?"":"_"+cid)).find(".selected").find('input[type=hidden]').first().val()){
+ if(cid==mtable){
+ $("#main_tableContent").find(".require-select").hide();
+ $("#main_tableContent").find("ul").first().find("a").first().click();
+ if($(".breadcrumb").children().length==4)
+ $(".breadcrumb").children().last().remove();
+
+ }else{
+ $("#sub_tableContent_"+cid).find(".require-select").hide();
+ $("#sub_tableContent_"+cid).find("ul").first().find("a").first().click();
+ }
+ return;
+ }
+ var fields=[]
+ var sizedFields=[];
+ var sizedFieldsAlias=[];
+ var notSizedFields=[];
+ for(var i in editSchema[cid]){
+ for(var j in editSchema[cid][i]){
+ //console.log(JSON.stringify(editSchema[cid][i][j]));
+ if(editSchema[cid][i][j]["ftype"]=="5"){
+ sizedFields.push(editSchema[cid][i][j]["name"].replace(/wkb_geometry/g,"geometry"));
+ sizedFieldsAlias.push(editSchema[cid][i][j]["id"]);
+ }
+ else{
+ notSizedFields.push(editSchema[cid][i][j]["name"].replace(/wkb_geometry/g,"geometry")+" AS \""+editSchema[cid][i][j]["id"]+"\"");
+ try{
+ var tmp=JSON.parse(editSchema[cid][i][j]["dependencies"]);
+ var sqlReq="";
+ var sqlClause="";
+ var sqlParams="";
+ var sqlParam=0;
+ var alphabet = 'abcdefghijklmnopqrstuvwxyz'.split('');
+ var hasDep=false;
+ var previousElements=[];
+ for(k in tmp)
+ for(l in tmp[k]){
+ //console.log(JSON.stringify(tmp[k][l]));
+ if(l=="myself"){
+ for(m in tmp[k][l])
+ for(n in tmp[k][l][m]){
+ //console.log(JSON.stringify(tmp[k][l][m][n]));
+ if(sqlReq!=""){
+ sqlReq+=", ";
+ sqlParams+=", ";
+ //sqlClause+=" WHERE "+alphabet[sqlParam-1]+"."+tmp[k][l][m][n]["tfield"]+"="+alphabet[sqlParam]+"."+tmp[k][l][m][n]["tfield"];
+
+ }
+ sqlReq+="("+cleanupTableName(tmp[k][l][m][n]["sql_query"])+") as "+alphabet[sqlParam];
+ sqlParams+=alphabet[sqlParam]+"."+tmp[k][l][m][n]["tfield"];
+ sqlParam+=1;
+ if(tmp[k][l][m][n]["dependents"]){
+ //console.log(JSON.stringify(tmp[k][l][m][n]["dependents"]));
+ for(var o in tmp[k][l][m][n]["dependents"]){
+ //console.log(JSON.stringify(tmp[k][l][m][n]["dependents"][o]));
+ for(var p in tmp[k][l][m][n]["dependents"][o]){
+ console.log(tmp[k][l][m][n]["dependents"][o][p]["sql_query"].replace(/from/,","+tmp[k][l][m][n]["dependents"][o][p]["tfield"]+" from"));
+ sqlReq+=", ("+cleanupTableName(tmp[k][l][m][n]["dependents"][o][p]["sql_query"]).replace(/from/,","+tmp[k][l][m][n]["dependents"][o][p]["tfield"]+" from")+") as b";
+ sqlParams+=", b."+tmp[k][l][m][n]["dependents"][o][p]["tfieldf"];
+ sqlParam+=2;
+ sqlReq+=", ("+cleanupTableName(editSchema[cid][i][j]["value"]).replace(/from/,","+tmp[k][l][m][n]["dependents"][o][p]["tfieldf"]+" from")+") as c";
+
+ sqlClause+=" WHERE c."+tmp[k][l][m][n]["dependents"][o][p]["tfieldf"]+"=b."+tmp[k][l][m][n]["dependents"][o][p]["tfieldf"];
+ sqlClause+=" AND b."+tmp[k][l][m][n]["tfield"]+"=a."+tmp[k][l][m][n]["tfield"];
+ sqlClause+=" AND c.id=(SELECT "+editSchema[cid][i][j]["name"]+" FROM "+cleanupTableName(allTables[cid].name)+" where id="+selectedId+")";
+ hasDep=true;
+ }
+ }
+ }
+ }
+ if(!hasDep){
+ var tmpReq=cleanupTableName(editSchema[cid][i][j]["value"]);
+ for(m in tmp[k][l])
+ for(n in tmp[k][l][m]){
+ tmpReq=tmpReq.replace(/from/,","+tmp[k][l][m][n]["tfield"]+" from");
+ if(sqlClause=="")
+ sqlClause+=" WHERE ";
+ else
+ sqlClause+=" "+tmp[k][l][m][n]["cond_join"]+" ";
+ sqlClause+=alphabet[m]+"."+tmp[k][l][m][n]["tfield"]+"=a1."+tmp[k][l][m][n]["tfield"];
+ sqlClause+="";
+ }
+ sqlClause+=" AND a1.id=(SELECT "+editSchema[cid][i][j]["name"]+" FROM "+cleanupTableName(allTables[cid].name)+" where id="+selectedId+")";
+ //sqlReq=(tmpReq);
+ console.log(tmpReq);
+ sqlReq+=", ("+tmpReq+") as a1";
+ }
+ //console.log(JSON.stringify(tmp[k][l]));
+ console.log("SELECT "+sqlParams+" FROM "+sqlReq+" "+sqlClause);
+ var localQuery="SELECT "+sqlParams+" FROM "+sqlReq+" "+sqlClause;
+ var res0=JSON.parse(window.Android.displayTable(localQuery,[]));
+ console.log(JSON.stringify(res0));
+ for(m in res0)
+ for(n in res0[m]){
+ try{
+ console.log("input[name=field_"+n+"],select[name=field_"+n+"],textarea[name=field_"+n+"]");
+ /**/
+ if($('.mm4me_edition').find("input[name=field_"+n+"],select[name=field_"+n+"],textarea[name=field_"+n+"]").first().length)
+ $('.mm4me_edition').find("input[name=field_"+n+"],select[name=field_"+n+"],textarea[name=field_"+n+"]").first().val(res0[m][n]).change();
+ else{
+ for(m0 in tmp[k][l])
+ for(n0 in tmp[k][l][m0]){
+ /*console.log(n0.indexOf(n)<0);
+ console.log(n);
+ console.log(n0);
+ console.log(res0[m][n]);*/
+ if(n0.indexOf(n)>=0){
+ console.log("input[name=field_"+n0+"],select[name=field_"+n0+"],textarea[name=field_"+n0+"]");
+ if(!$('.mm4me_edition').find("input[name=field_"+n+"],select[name=field_"+n+"],textarea[name=field_"+n+"]").first().length)
+ $('.mm4me_edition').find("input[name=field_"+n0+"],select[name=field_"+n0+"],textarea[name=field_"+n0+"]").first().val(res0[m][n]).change();
+ }
+ }
+ }
+ }catch(e){
+ console.log(e);
+ }
+
+ }
+ //if(tmp[k][l]["dependents"])
+ }else{
+ console.log(JSON.stringify(tmp[k][l]));
+ if(tmp[k][l]["tfield"]=="none"){
+ var isNull=JSON.parse(window.Android.displayTable("SELECT CASE WHEN "+l+" is null THEN 1 ELSE 0 END as p FROM "+cleanupTableName(allTables[cid].name)+" where id="+selectedId,[]));
+ console.log(JSON.stringify(isNull));
+ if(isNull[0]["p"]=="0"){
+ console.log(JSON.stringify(isNull));
+ console.log("input[name=field_"+editSchema[cid][i][j]["id"]+"],select[name=field_"+editSchema[cid][i][j]["id"]+"],textarea[name=field_"+editSchema[cid][i][j]["id"]+"]");
+ console.log(k+"");
+ $('.mm4me_edition').find("input[name=field_"+editSchema[cid][i][j]["id"]+"],select[name=field_"+editSchema[cid][i][j]["id"]+"],textarea[name=field_"+editSchema[cid][i][j]["id"]+"]").first().val(k+"").change();
+ }
+ }
+
+ }
+ }
+
+ //if(sqlReq)
+ }catch(e){
+ console.log(e);
+ }
+ }
+ if(editSchema[cid][i][j]["name"].indexOf("unamed")<0)
+ fields.push(editSchema[cid][i][j]["name"].replace(/wkb_geometry/g,"geometry")+" AS \""+editSchema[cid][i][j]["id"]+"\"");
+ }
+ }
+ var ccol=getPKey(cleanupTableName(allTables[cid].name));
+ /* $("#exampleTable"+(cid==mtable?"":"_"+cid)).find(".selected").find('input[type=hidden]').first().val() */
+ var editValues;
+ if(sizedFields.length>0){
+ for(var i=0;i 1000000 and "+ccol+"="+selectedId,[]));
+ //console.log(JSON.stringify(hasElement[0]));
+ if(hasElement[0]["cnt"]!="0"){
+ var nbIteration=parseInt(hasElement[0]["len"])/1000000;
+ var zfields=[]
+ var len=0;
+ var query="";
+ var len1=parseInt(hasElement[0]["len"]);
+ for(var j=0;j0){
+ $("#field_"+j+"_map").show();
+ $("#field_"+j+"_map").off('click');
+ $("#field_"+j+"_map").on('click',function(){
+ console.log("Display table with a selected feature");
+ console.log($(this).prev().val());
+ showElementOnMap($(this).prev().val());
+ });
+ }
+ if($(".btn_field_"+j+"_lat").length>0){
+ //alert(editValues[i][j]);
+ $(".btn_field_"+j+"_lat").html(editValues[i][j]);
+ $(".btn_field_"+j+"_long").html("");
+ $(".btn_field_"+j+"_source").html("");
+ $("input[name='field_"+j+"']").val("POINT"+editValues[i][j]);
+
+ }
+ }
+ //$(".swagEditor").summernote();
+ }
+ }
+
+ for(var i in editSchema[cid]){
+ for(var j in editSchema[cid][i]){
+ if(editSchema[cid][i][j]["name"].indexOf("unamed")>=0){
+ if(editSchema[cid][i][j]["ftype"]==6){
+ var tmp=editSchema[cid][i][j]["value"].split(';');
+ var list=JSON.parse(window.Android.displayTable("select "+tmp[1]+" from "+cleanupTableName(tmp[2])+" where "+tmp[0]+"=(SELECT id from "+cleanupTableName(tblName)+" WHERE ogc_fid="+selectedId+")",[]));
+ editValues["0"][editSchema[cid][i][j]["id"]]=list;
+ for(var k in list){
+ for(var l in list[k]){
+ $('.mm4me_edition').find("select[name=field_"+editSchema[cid][i][j]["id"]+"]").find('option').each(function(){
+ if($(this).val()==list[k][l])
+ $(this).prop("selected",true);
+ });
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if(cid==mtable){
+ if($(".breadcrumb").children().length==4)
+ $(".breadcrumb").children().last().remove();
+ $(".breadcrumb").append(' '+editValues["0"]["local_id"]+' ');
+ }
+ ((cid==mtable)?$(".require-select"):$("#sub_tableContent_"+cid).find(".require-select")).first().show();
+ ((cid==mtable)?$(".require-select"):$("#sub_tableContent_"+cid).find(".require-select")).first().find("a").first().click();
+
+ if(toRunOnLoad[cid])
+ for(var i=0;i=0 order by mm4me_editions.step asc",[]);
+ if(MM4ME_DEBUG)
+ console.log(list);
+ list=JSON.parse(list);
+ if(MM4ME_DEBUG)
+ console.log((!allTables[tblId]));
+ if(!allTables[tblId]){
+ allTables[tblId]={"id":id,"name":name,"title":title};
+ }
+ mtable=tblId;
+
+ $(".mm4me_edition").find("ul").first().html("");
+ $(".mm4me_edition").find(".well").first().html("");
+ var cnt=0;
+ for(var i in list){
+ lastEdition[list[i]["id"]]=list[i];
+ var cid=(i==0?list[i]["id"]+"_0":list[i]["id"]);
+ $(".mm4me_edition").find("ul").first().append(''+list[i]["name"]+' ');
+ printEditionFields(list[i],$("#edition_form_edit"),cid,mainTable[id]);
+ }
+ if(list.length==1)
+ $(".mm4me_edition").find("ul").first().hide();
+
+ var aCnt=0;
+ $('.mm4me_edition').find('ul').first().find('a').each(function () {
+ if(aCnt>0)
+ $(this).parent().addClass('require-select');
+ aCnt+=1;
+ });
+ $(".require-select").hide();
+ $('.mm4me_listing').find('ul').first().find('a').first().click();
+ $('.mm4me_edition').find('ul').find('a').first().click();
+ $('.swagEditor').summernote();
+ $(".mm-act-add").click(function(){
+ runInsertQuery($(this).parent().parent(),mainTable[id],editOnlyTableReact);
+ });
+ $(".mm-act-save").click(function(){
+ runUpdateQuery($(this).parent().parent(),mainTable[id],editOnlyTableReact);
+ });
+ $(".breadcrumb").children().last().remove();
+ $(".breadcrumb").append(' '+window.Android.translate('edit')+' ');
+ $(".breadcrumb").append(' '+tblTitle+' ');
+
+ setTimeout(function() { updateChangingFields(changingFields) }, 1500);
+
+ /*for(var i=0;i';
+ else
+ cStr+=changingField[j][ckey]["values"][cIndex][i][lkey]+'';
+ cnt+=1;
+ }
+ $("select[name=field_"+changingField[j][ckey]["id"]+"]").append(cStr);
+ }
+ if(i==0)
+ $("select[name=field_"+changingField[j][ckey]["id"]+"]").html(''+window.Android.translate('none')+' ');
+ }
+ }
+ };
+ };
+ $("select[name=field_"+key+"]").off('change');
+ $("select[name=field_"+key+"]").change(localFunc(changingFields[i][key]["dep"]));
+ $("select[name=field_"+key+"]").change();
+ }
+ }*/
+ $('.mm4me_listing').show();
+ $('.mm4me_content').hide();
+
+}
+
+/*****************************************************************************
+ * In case no server is configured
+ *****************************************************************************/
+function displayNoListing(){
+ $.ajax({
+ method: "GET",
+ url: 'content/nolisting.html',
+ success: function(data){
+ if(MM4ME_DEBUG)
+ console.log('Display warning message on the UI !');
+ $(".mm4me_content").html(data);
+ $(".mm4me_content").find(".pannel-body").find("p").first().html(window.Android.translate("mm_no_db_found"));
+ },
+ error: function(){
+ window.Android.showToast("error !");
+ }
+ });
+}
+
+
+/*****************************************************************************
+ * Authenticate a user
+ *****************************************************************************/
+function authenticate(url,login,passwd,func,func1){
+ var curl=url+"?service=WPS&request=Execute&version=1.0.0&Identifier=authenticate.clogIn&DataInputs=login="+login+";password="+passwd+"&RawDataOutput=Result";
+ if(MM4ME_DEBUG)
+ console.log(curl);
+ $.ajax({
+ method: "GET",
+ url: curl,
+ success: function(data){
+ if(MM4ME_DEBUG)
+ console.log(data);
+ if(func){
+ console.log("Call func!")
+ func();
+ }
+ },
+ error: function(){
+ if(func1){
+ func1();
+ }
+ else{
+ disconnect(url);
+ if(MM4ME_DEBUG)
+ console.log("unable to login!");
+ var hasBeenShown=false;
+ var xml=arguments[0].responseText;
+ $(xml).find("ows\\:ExceptionText").each(function(){
+ window.Android.showToast($(this).text());
+ hasBeenShown=true;
+ });
+ if(!hasBeenShown){
+ window.Android.showToast(JSON.stringify(arguments));
+ }
+ }
+
+ }
+ });
+}
+
+/*****************************************************************************
+ * Disconnect a user
+ *****************************************************************************/
+function disconnect(url,func,func1){
+ var curl=url+"?service=WPS&request=Execute&version=1.0.0&Identifier=authenticate.clogOut&DataInputs=&RawDataOutput=Result";
+ if(MM4ME_DEBUG)
+ console.log(curl);
+ $.ajax({
+ method: "GET",
+ url: curl,
+ success: function(data){
+ if(MM4ME_DEBUG){
+ console.log(data);
+ console.log("** Your are no more connected!");
+ }
+ if(func){
+ func();
+ }
+ },
+ error: function(){
+ if(MM4ME_DEBUG){
+ console.log(curl);
+ console.log("unable to disconnect!");
+ }
+ if(func1){
+ func1();
+ }
+ }
+ });
+}
+
+var geometries={"line":{"geom":null,"constructor": "ol.geom.Linestring"},"polygon":{"geom":null,"constructor":"ol.geom.Polygon","cline":null}};
+var stopTracking=false;
+var currentGeometry="line";
+var currentGeometryField="none";
+var map=null;
+var vectorLayer,vectorLayer1;
+var position;
+
+/*****************************************************************************
+ * Track modification of the GPS location
+ *****************************************************************************/
+function trackCurrentGPSPosition(){
+ console.log("## trackCurrentGPSPosition");
+ updateCurrentMapLocation();
+
+ var tmp0=geometries["origin"].getCoordinates();
+ if(geometries[currentGeometry]["geom"]!=null){
+ var tmp1=geometries[currentGeometry]["geom"].getCoordinates();
+ tmp0=tmp1[tmp1.length-1];
+ }
+ tmp=ol.proj.transform([position[bestIndex].lon,position[bestIndex].lat], 'EPSG:4326','EPSG:3857');
+ console.log(JSON.stringify(tmp0));
+ console.log(JSON.stringify(tmp));
+ $("#currentPosition").html("Position: "+position[bestIndex].lon.toFixed(6)+","+position[bestIndex].lat.toFixed(6)+" ");
+ if(tmp0[0]==tmp[0] && tmp0[1]==tmp[1]){
+ console.log(" !!!!!!!! Same position!!!!!!!! ");
+ if(!stopTracking)
+ setTimeout(function() { trackCurrentGPSPosition();}, 1000);
+ return;
+ }
+
+ console.log(JSON.stringify(tmp0));
+ console.log(JSON.stringify(tmp));
+ if(geometries[currentGeometry]["geom"]==null){
+ //geometries[currentGeometry]=new geometries[currentGeometry]["constructor"]([tmp0,tmp]);
+ if(currentGeometry=="line")
+ geometries[currentGeometry]["geom"]=new ol.geom.LineString([tmp0,tmp]);
+ else
+ geometries[currentGeometry]["geom"]=new ol.geom.LineString([tmp0,tmp]);
+ }else{
+ if(currentGeometry=="line"){
+ tmpCoordinates=geometries[currentGeometry]["geom"].getCoordinates();
+ tmpCoordinates.push(tmp);
+ geometries[currentGeometry]["geom"]=new ol.geom.LineString(tmpCoordinates);
+ }
+ else{
+ if(geometries[currentGeometry]["cline"]==null)
+ tmpCoordinates=geometries[currentGeometry]["geom"].getCoordinates();
+ else
+ tmpCoordinates=geometries[currentGeometry]["cline"].getCoordinates();
+ console.log(JSON.stringify(tmpCoordinates));
+ tmpCoordinates.push(tmp);
+ console.log(JSON.stringify(tmpCoordinates));
+ if(tmpCoordinates.length>2){
+ geometries[currentGeometry]["cline"]=new ol.geom.LineString(tmpCoordinates);
+ tmpCoordinates.push(tmpCoordinates[0]);
+ console.log(JSON.stringify(tmpCoordinates));
+ geometries[currentGeometry]["geom"]=new ol.geom.Polygon();
+ console.log(JSON.stringify(tmpCoordinates));
+ geometries[currentGeometry]["geom"].appendLinearRing(new ol.geom.LinearRing(tmpCoordinates));
+ console.log(JSON.stringify(tmpCoordinates));
+ }
+ }
+
+ }
+ console.log(JSON.stringify(tmp0));
+ console.log(JSON.stringify(tmp));
+ try{
+ console.log(JSON.stringify(geometries[currentGeometry]["geom"].getCoordinates()));
+ var feature=new ol.Feature({geometry: geometries[currentGeometry]["geom"],"name":"myFeature"});
+ vectorLayer1.getSource().clear();
+ vectorLayer1.getSource().addFeatures([feature]);
+ var wkt=new ol.format.WKT();
+ var tmpGeometry=geometries[currentGeometry]["geom"].clone().transform('EPSG:3857', 'EPSG:4326');
+ console.log(wkt.writeGeometry(tmpGeometry));
+ $("input[name='"+currentGeometryField+"']").val(wkt.writeGeometry(tmpGeometry));
+ }catch(e){
+ console.log(e);
+ }
+ console.log(JSON.stringify(tmp0));
+ console.log(JSON.stringify(tmp));
+ if(!stopTracking)
+ setTimeout(function() { trackCurrentGPSPosition();}, 1000);
+ console.log(JSON.stringify(tmp0));
+ console.log(JSON.stringify(tmp));
+}
+
+var modalCallback=null;
+
+function trackStepCurrentGPSPosition(){
+ console.log("## trackStepCurrentGPSPosition");
+ updateCurrentMapLocation();
+ $("#currentPosition").html("Position: "+position[bestIndex].lon.toFixed(6)+","+position[bestIndex].lat.toFixed(6)+" ");
+ if(!stopTracking)
+ setTimeout(function() { trackStepCurrentGPSPosition();}, 1000);
+}
+
+function addCurrentLocation(){
+ console.log("addCurrentLocation!!!!");
+ if(geometries["origin"]==null)
+ geometries["origin"]=new ol.geom.Point(ol.proj.transform([position[bestIndex].lon,position[bestIndex].lat], 'EPSG:4326','EPSG:3857'));
+ else{
+ var tmp0=geometries["origin"].getCoordinates();
+ if(geometries[currentGeometry]["geom"]!=null){
+ var tmp1=geometries[currentGeometry]["geom"].getCoordinates();
+ tmp0=tmp1[tmp1.length-1];
+ }
+ tmp=ol.proj.transform([position[bestIndex].lon,position[bestIndex].lat], 'EPSG:4326','EPSG:3857');
+ console.log(JSON.stringify(tmp0));
+ console.log(JSON.stringify(tmp));
+ $("#currentPosition").html("Position: "+position[bestIndex].lon.toFixed(6)+","+position[bestIndex].lat.toFixed(6)+" ");
+ if(geometries[currentGeometry]["geom"]==null){
+ //geometries[currentGeometry]=new geometries[currentGeometry]["constructor"]([tmp0,tmp]);
+ if(currentGeometry=="line")
+ geometries[currentGeometry]["geom"]=new ol.geom.LineString([tmp0,tmp]);
+ else
+ geometries[currentGeometry]["geom"]=new ol.geom.LineString([tmp0,tmp]);
+ }else{
+ if(currentGeometry=="line"){
+ tmpCoordinates=geometries[currentGeometry]["geom"].getCoordinates();
+ tmpCoordinates.push(tmp);
+ geometries[currentGeometry]["geom"]=new ol.geom.LineString(tmpCoordinates);
+ }
+ else{
+ if(geometries[currentGeometry]["cline"]==null)
+ tmpCoordinates=geometries[currentGeometry]["geom"].getCoordinates();
+ else
+ tmpCoordinates=geometries[currentGeometry]["cline"].getCoordinates();
+ console.log(JSON.stringify(tmpCoordinates));
+ tmpCoordinates.push(tmp);
+ console.log(JSON.stringify(tmpCoordinates));
+ if(tmpCoordinates.length>2){
+ geometries[currentGeometry]["cline"]=new ol.geom.LineString(tmpCoordinates);
+ tmpCoordinates.push(tmpCoordinates[0]);
+ console.log(JSON.stringify(tmpCoordinates));
+ geometries[currentGeometry]["geom"]=new ol.geom.Polygon();
+ console.log(JSON.stringify(tmpCoordinates));
+ geometries[currentGeometry]["geom"].appendLinearRing(new ol.geom.LinearRing(tmpCoordinates));
+ console.log(JSON.stringify(tmpCoordinates));
+ }
+ }
+
+ }
+ try{
+ console.log(JSON.stringify(geometries[currentGeometry]["geom"].getCoordinates()));
+ var feature=new ol.Feature({geometry: geometries[currentGeometry]["geom"],"name":"myFeature"});
+ vectorLayer1.getSource().clear();
+ vectorLayer1.getSource().addFeatures([feature]);
+ var wkt=new ol.format.WKT();
+ var tmpGeometry=geometries[currentGeometry]["geom"].clone().transform('EPSG:3857', 'EPSG:4326');
+ console.log(wkt.writeGeometry(tmpGeometry));
+ $("input[name='"+currentGeometryField+"']").val(wkt.writeGeometry(tmpGeometry));
+ }catch(e){
+ console.log(e);
+ }
+
+ }
+}
+
+function trackStepByStepPosition(elem,ltype){
+ modalCallback=function(){
+ //$("#myModal").find(".glyphicon-ok").parent().show();
+ if(myVectorLayer)
+ myVectorLayer.getSource().clear();
+ geometries["origin"]=null;
+ setTimeout(function() { trackStepCurrentGPSPosition();}, 1000);
+ $("#myModal").find("h4").html(' '+window.Android.translate("gps_track"));
+ $("#myModal").find(".modal-footer").html(
+ ' Cancel '+
+ ' Add '+
+ ' Save '
+ );
+ };
+ currentGeometry=ltype;
+ currentGeometryField=elem;
+ geometries[ltype]["geom"]=null;
+ geometries[ltype]["cline"]=null;
+ if(!$("#map").length)
+ $.ajax({
+ method: "GET",
+ url: './map-modal.html',
+ error: function(){
+ },
+ success: function(data){
+ console.log(data);
+ $("body").append(data);
+ $("#map").css("height",($(window).height()-220)+"px");
+ $('#myModal').modal();
+ addOptionalLocalTiles(true);
+ $('#myModal').on('shown.bs.modal', function () {
+ console.log($("#mmm4me_ls").length);
+ initMapToLocation();
+ localTileIndex=map.getLayers().getLength();
+ /*geometries["origin"]=new ol.geom.Point(ol.proj.transform([position[bestIndex].lon,position[bestIndex].lat], 'EPSG:4326','EPSG:3857'));
+ setTimeout(function() { trackCurrentGPSPosition();}, 1000);*/
+ modalCallback();
+ });
+ $('#myModal').on('hide.bs.modal', function () {
+ console.log("HIDE");
+ stopTracking=true;
+ });
+ }
+ });
+ else{
+ stopTracking=false;
+ $('#myModal').modal();
+ vectorLayer1.getSource().clear();
+ updateCurrentMapLocation();
+ geometries["origin"]=null;
+ setTimeout(function() { trackStepCurrentGPSPosition();}, 1000);
+ }
+}
+/*****************************************************************************
+ * Start tracking GPS location
+ *****************************************************************************/
+function trackGPSPosition(elem,ltype){
+ modalCallback=function(){
+ //$("#myModal").find(".glyphicon-ok").parent().show();
+ if(myVectorLayer)
+ myVectorLayer.getSource().clear();
+ geometries["origin"]=new ol.geom.Point(ol.proj.transform([position[bestIndex].lon,position[bestIndex].lat], 'EPSG:4326','EPSG:3857'));
+ setTimeout(function() { trackCurrentGPSPosition();}, 1000);
+ $("#myModal").find("h4").html(' '+window.Android.translate("gps_track"));
+ $("#myModal").find(".modal-footer").html(
+ ' Cancel '+
+ ' Save '
+ );
+ };
+ currentGeometry=ltype;
+ currentGeometryField=elem;
+ geometries[ltype]["geom"]=null;
+ geometries[ltype]["cline"]=null;
+ if(!$("#map").length)
+ $.ajax({
+ method: "GET",
+ url: './map-modal.html',
+ error: function(){
+ },
+ success: function(data){
+ console.log(data);
+ $("body").append(data);
+ $("#map").css("height",($(window).height()-220)+"px");
+ $('#myModal').modal();
+ addOptionalLocalTiles(true);
+ $('#myModal').on('shown.bs.modal', function () {
+ console.log($("#mmm4me_ls").length);
+ initMapToLocation();
+ localTileIndex=map.getLayers().getLength();
+ /*geometries["origin"]=new ol.geom.Point(ol.proj.transform([position[bestIndex].lon,position[bestIndex].lat], 'EPSG:4326','EPSG:3857'));
+ setTimeout(function() { trackCurrentGPSPosition();}, 1000);*/
+ modalCallback();
+ });
+ $('#myModal').on('hide.bs.modal', function () {
+ console.log("HIDE");
+ stopTracking=true;
+ });
+ }
+ });
+ else{
+ stopTracking=false;
+ $('#myModal').modal();
+ vectorLayer1.getSource().clear();
+ updateCurrentMapLocation();
+ geometries["origin"]=new ol.geom.Point(ol.proj.transform([position[bestIndex].lon,position[bestIndex].lat], 'EPSG:4326','EPSG:3857'));
+ setTimeout(function() { trackCurrentGPSPosition();}, 1000);
+ }
+}
+
+var hasAddedElement=0;
+var myVectorLayer=null;
+function addSelectedElement(wktString){
+ var features=[];
+ try{
+ var format = new ol.format.WKT();
+ features.push(
+ format.readFeature(wktString, {
+ dataProjection: 'EPSG:4326',
+ featureProjection: 'EPSG:3857'
+ })
+ );
+ }catch(e){
+ console.log("Unable to parse WKT ?!"+e)
+ }
+ if(!vectorLayer1){
+ myVectorLayer = new ol.layer.Vector({
+ source: new ol.source.Vector({
+ features: features
+ }),
+ style: new ol.style.Style({
+ stroke: new ol.style.Stroke({
+ color: "#3333aa",
+ width: 1.4
+ })
+ })
+ });
+ map.addLayer(myVectorLayer);
+ }
+ else{
+ vectorLayer1.getSource().clear();
+ vectorLayer1.getSource().addFeatures(features);
+ }
+ console.log(vectorLayer1.getSource().getExtent());
+ map.updateSize();
+ map.getView().fit(vectorLayer1.getSource().getExtent(),map.getSize());
+ hasAddedElement=1;
+}
+
+/*****************************************************************************
+ * Show selected feature on map
+ *****************************************************************************/
+ var addSelectedIndex=0;
+ var mySelectedElement=null;
+function showElementOnMap(wktString){
+ mySelectedElement=wktString;
+ modalCallback=function(){
+ //$("#myModal").find(".glyphicon-ok").parent().hide();
+ if(addSelectedIndex==0){
+ updateCurrentMapLocation();
+ $("#currentPosition").html("Position: "+position[bestIndex].lon.toFixed(6)+","+position[bestIndex].lat.toFixed(6)+" ");
+ }
+ addSelectedElement(mySelectedElement);
+ $("#myModal").find("h4").html(' '+window.Android.translate("view_feature"));
+ addSelectedIndex++;
+ };
+ //$("#currentPosition").html("Position: "+position[bestIndex].lon.toFixed(6)+","+position[bestIndex].lat.toFixed(6)+" ");
+ if(!$("#map").length)
+ $.ajax({
+ method: "GET",
+ url: './map-modal.html',
+ error: function(){
+ },
+ success: function(data){
+ console.log(data);
+ $("body").append(data);
+ $("#map").css("height",($(window).height()-220)+"px");
+ $('#myModal').modal();
+ addOptionalLocalTiles(true);
+ $('#myModal').on('shown.bs.modal', function () {
+ console.log($("#mmm4me_ls").length);
+ initMapToLocation();
+ localTileIndex=map.getLayers().getLength();
+ modalCallback();
+ });
+ $('#myModal').on('hide.bs.modal', function () {
+ console.log("HIDE");
+ stopTracking=true;
+ });
+ }
+ });
+ else{
+ console.log("OK ");
+ $('#myModal').modal();
+ console.log("OK ");
+ vectorLayer1.getSource().clear();
+ console.log("OK ");
+ updateCurrentMapLocation();
+ console.log("OK ");
+ addSelectedElement(wktString);
+ console.log("OK ");
+ }
+
+ $("#currentPosition").html("Position: "+position[bestIndex].lon.toFixed(6)+","+position[bestIndex].lat.toFixed(6)+" ");
+}
+
+
+/*****************************************************************************
+ * Store the current GPS location in the edit form
+ *****************************************************************************/
+function requireGPSPosition(elem){
+ var position=JSON.parse(window.Android.getGPS());
+ //elem=$("#"+elem);
+ if(position.lat){
+ $(".btn_"+elem+"_lat").html(position.lat);
+ $(".btn_"+elem+"_long").html(position.lon);
+ $(".btn_"+elem+"_source").html(position.source);
+ $("input[name='"+elem+"']").val("POINT("+position.lon+" "+position.lat+")");
+ }else{
+
+ }
+ console.log(JSON.stringify(position));
+}
+
+/*****************************************************************************
+ * Ajax setup to ensure seting "withCredentials" to true
+ *****************************************************************************/
+function ajaxSetup(){
+ $.ajaxSetup({
+ xhrFields: {
+ withCredentials: true
+ }
+ });
+}
+
+/*****************************************************************************
+ * Get the current Network and GPS availability
+ *****************************************************************************/
+function getCurrentStatus(){
+ var tmp=JSON.parse(window.Android.getGNStatus());
+ tmp["gps"]=JSON.parse(tmp["gps"]);
+ updateStatus(tmp['gps'],tmp['net']);
+}
+
+/*****************************************************************************
+ * Display the status icons
+ *****************************************************************************/
+function addStatusControl(){
+ $('.breadcrumb').append(' /'+
+ ''+
+ ''+
+ ''+
+ ' '+
+ ' ');
+}
+
+/*****************************************************************************
+ * Initialize the map and show the current GPS location
+ *****************************************************************************/
+var localTiles,localTiles0;
+var tileLayers=[];
+function initMapToLocation(){
+ if(map)
+ return;
+ var osmSource = new ol.source.OSM();
+
+ var otherLayers=[];
+ var otherLayersSwitcher="";
+ try{
+ var BasesLayersStr=window.Android.getBaseLayers();
+ console.log(BasesLayersStr);
+ var BasesLayers=JSON.parse(BasesLayersStr);
+ for(var i=0;i=4)
+ map.getLayers().item(otherLayers[i]["index"]).setVisible(false);
+ }else{
+ $(this).parent().find("i").removeClass("glyphicon-eye-close").addClass("glyphicon-eye-open");
+ $("#dmopacity_"+i).show();
+ console.log(JSON.stringify(map.getLayers().getLength()));
+ if(!otherLayers[i]["index"]){
+ var myTileLayer=new ol.layer.Tile({source: otherLayers[i]["olLayer"]});
+ var layers = map.getLayers();
+ layers.insertAt(tileLayers.length+1,myTileLayer);
+ otherLayers[i]["index"]=tileLayers.length+1;
+ tileLayers.push(myTileLayer);
+ if(localTileIndex>0)
+ localTileIndex+=1;
+ }
+ else
+ map.getLayers().item(otherLayers[i]["index"]).setVisible(true);
+ }
+ });
+ $(".map").parent().find("input[type=range]").last().on('change',function(){
+ console.log("change to "+$(this).val());
+ map.getLayers().item(otherLayers[i]["index"]).setOpacity($(this).val()/100);
+ });
+
+ })(i);
+ $(".map").parent().find("input[type=checkbox]").parent().off('click');
+ $(".map").parent().find("input[type=checkbox]").parent().on('click',function(){
+ var tmp=$(this).find("input[type=checkbox]");
+ if(tmp.is(":checked")){
+ $(this).find("input[type=checkbox]").prop("checked",false).change();
+ $(this).addClass("select");
+ }
+ else{
+ $(this).find("input[type=checkbox]").prop("checked",true).change();
+ $(this).removeClass("select");
+ }
+ });
+
+ },1);
+ })(i)
+ }
+ }
+ map = new ol.Map({
+ layers: layers,
+ target: 'map',
+ controls: ol.control.defaults({
+ attributionOptions: /** @type {olx.control.AttributionOptions} */ ({
+ collapsible: true
+ })
+ }),
+ view: new ol.View({
+ center: ol.proj.transform([0,0], 'EPSG:4326', 'EPSG:3857'),
+ zoom: 14,
+ maxZoom: 22,
+ minZoom: 1
+ })
+ });
+
+ var bstyle = function (feature, resolution) {
+
+ /*var iconFont = 'glyphicon';
+ var iconFontText = '\e062';*/
+ //var iconFont = 'glyphicon';
+ var iconFont = 'Glyphicons Halflings';
+ var iconFontText = '\ue062';
+ var iconSize = 24;
+ var opacity=0.4;
+ var col = 'rgba(0,255,0,0.6)';
+ if(feature.get("source")=="GPS")
+ col = 'rgba(41,136,54,0.5)';//#298836
+ else if(feature.get("source")=="Network"){
+ col = 'rgba(91,176,75,0.4)';//#5bb04b
+ iconSize = 32;
+ opacity=0.2;
+ }
+ else if(feature.get("source")=="other"){
+ col='rgba(129,208,113,0.5)';//#81d071
+ iconSize = 36;
+ opacity=0.2;
+ }
+ else
+ col='rgba(166,63,39,0.5)'; //#a63f27 //"#EE0000";
+ var styles = [];
+
+ var styleIcon = new ol.style.Style({
+ text: new ol.style.Text({
+ font: 'Normal ' + iconSize + 'px ' + iconFont,
+ text: iconFontText,
+ fill: new ol.style.Fill({ color: col })
+ })
+ });
+ styles.push(styleIcon);
+
+ //console.log(feature.get("type"));
+ return styles;
+ /*return function (feature, resolution) {
+ styles.styleIcon.getText().setText(feature.get("iconCode"));
+ return styles;
+ };*/
+ };
+ position=JSON.parse(window.Android.getFullGPS());
+ //console.log(JSON.stringify(position));
+ if(position.length==0){
+ position=[{lon:3.5,lat:43.5,source:"none"}];
+ }
+ var iconFeatures = [];
+ for(var i=0;i=4)
+ map.getLayers().item(localTileIndex).setVisible(false);
+ }else{
+ $(this).parent().find("i").removeClass("glyphicon-eye-close").addClass("glyphicon-eye-open");
+ $("#dmopacity").show();
+ console.log(JSON.stringify(map.getLayers().getLength()));
+ if(map.getLayers().getLength()0.05){
+ map.getView().setRotation(-direction);
+ oldBearer=direction;
+ }
+ }else{
+ window.Android.stopReportDirection();
+ }
+}
diff --git a/app/src/main/assets/stroller.sfb b/app/src/main/assets/stroller.sfb
new file mode 100644
index 0000000..535e46c
Binary files /dev/null and b/app/src/main/assets/stroller.sfb differ
diff --git a/app/src/main/assets/trigrid.png b/app/src/main/assets/trigrid.png
new file mode 100755
index 0000000..05cbe6e
Binary files /dev/null and b/app/src/main/assets/trigrid.png differ
diff --git a/app/src/main/assets/wheelchair.sfb b/app/src/main/assets/wheelchair.sfb
new file mode 100644
index 0000000..fc2ecbd
Binary files /dev/null and b/app/src/main/assets/wheelchair.sfb differ
diff --git a/app/src/main/java/Ar_Simulation/ARSimulation.java b/app/src/main/java/Ar_Simulation/ARSimulation.java
new file mode 100644
index 0000000..e7e6ee5
--- /dev/null
+++ b/app/src/main/java/Ar_Simulation/ARSimulation.java
@@ -0,0 +1,326 @@
+package Ar_Simulation;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.os.Build;
+import android.os.Build.VERSION_CODES;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.ListPopupWindow;
+import android.widget.SeekBar;
+import android.widget.Spinner;
+import android.widget.Toast;
+
+import android.support.v7.app.AppCompatActivity;
+
+import com.google.ar.core.Anchor;
+import com.google.ar.core.HitResult;
+import com.google.ar.core.Plane;
+import com.google.ar.core.Pose;
+import com.google.ar.sceneform.AnchorNode;
+import com.google.ar.sceneform.math.Quaternion;
+import com.google.ar.sceneform.math.Vector3;
+import com.google.ar.sceneform.rendering.ModelRenderable;
+import com.google.ar.sceneform.ux.ArFragment;
+import com.google.ar.sceneform.ux.TransformableNode;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+
+import fr.geolabs.dev.mapmint4me.R;
+
+import static java.lang.Math.atan2;
+
+public class ARSimulation extends AppCompatActivity {
+ private static final String TAG = ARSimulation.class.getSimpleName();
+ private static final double MIN_OPENGL_VERSION = 3.0;
+ private float x=0f, y=0f, z=0f;
+ private ArFragment arFragment;
+ private AnchorNode myanchornode;
+ TransformableNode mytranode = null;
+
+ private SeekBar sb_size;
+ private Spinner spn_model;
+
+ private HitResult myhit;
+ private float mySize = 70f;
+ private float mytravel=0.01f, distance_x=0f, distance_z=0f, myangle=0f;
+
+
+ int[] sfb_source = {R.raw.wheelchair, R.raw.model, R.raw.stroller, R.raw.cart};
+ String[] arr_models = {"Wheelchair", "Arrow", "Stroller", "Shopping cart"};
+ private ModelRenderable[] renderable_models = new ModelRenderable[sfb_source.length];
+
+ @Override
+ @SuppressWarnings({"AndroidApiChecker", "FutureReturnValueIgnored"})
+ // CompletableFuture requires api level 24
+
+ // FutureReturnValueIgnored is not valid
+ protected void onCreate(Bundle savedInstanceState) {
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ super.onCreate(savedInstanceState);
+
+ if (!checkIsSupportedDeviceOrFinish(this)) {
+ return;
+ }
+ try
+ {
+ this.getSupportActionBar().hide();
+ }
+ catch (NullPointerException e){}
+ setContentView(R.layout.ar_simulation);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ arFragment = (ArFragment) getSupportFragmentManager().findFragmentById(R.id.ux_fragment);
+
+ Button r_left = (Button)findViewById(R.id.r_left);
+ Button r_right = (Button)findViewById(R.id.r_right);
+ Button accelerate = (Button)findViewById(R.id.accelerate);
+ spn_model = (Spinner) findViewById(R.id.spn_model);
+ sb_size = (SeekBar) findViewById(R.id.sb_size);
+ List anchorNodes = new ArrayList<>();
+
+ sb_size.setEnabled(false);
+
+ // Keep immersive view on spinner opening
+ Field popup = null;
+ try {
+ popup = Spinner.class.getDeclaredField("mPopup");
+ popup.setAccessible(true);
+ ListPopupWindow popupWindow = (ListPopupWindow) popup.get(spn_model);
+ popupWindow.setModal(false);
+ } catch (NoSuchFieldException e) {
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ }
+
+
+
+ sb_size.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ mySize = progress;
+ myanchornode.setLocalScale(new Vector3(progress/70f, progress/70f, progress/70f));
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ }
+ });
+
+ accelerate.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (mytranode != null) {
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ myangle = set(mytranode.getLocalRotation());
+ }
+ if (event.getAction() == MotionEvent.ACTION_UP) {
+ }
+ forward(myanchornode);
+ }
+ return true;
+
+ }
+ });
+
+ r_left.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if(mytranode != null){
+ Quaternion q1 = mytranode.getLocalRotation();
+ Quaternion q2 = Quaternion.axisAngle(new Vector3(0, 1f, 0f), .5f);
+ mytranode.setLocalRotation(Quaternion.multiply(q1, q2));
+ myangle = set(mytranode.getLocalRotation());
+ }
+
+ return true;
+ }
+
+ });
+
+ r_right.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if(mytranode != null){
+ myangle+=0.01f;
+ Quaternion q1 = mytranode.getLocalRotation();
+ Quaternion q2 = Quaternion.axisAngle(new Vector3(0, 1f, 0f), -.5f);
+ mytranode.setLocalRotation(Quaternion.multiply(q1, q2));
+ myangle = set(mytranode.getLocalRotation());
+ }
+
+ return true;
+ }
+
+ });
+
+ for(int i = 0 ; i < sfb_source.length ; i++) {
+ int finalI = i;
+ ModelRenderable.builder()
+ .setSource(this, sfb_source[i])
+ .build()
+ .thenAccept(renderable -> renderable_models[finalI] = renderable)
+ .exceptionally(
+ throwable -> {
+ Toast toast =
+ Toast.makeText(this, "Unable to load andy renderable", Toast.LENGTH_LONG);
+ toast.setGravity(Gravity.CENTER, 0, 0);
+ toast.show();
+ return null;
+ });
+ }
+
+
+ arFragment.setOnTapArPlaneListener(
+ (HitResult hitResult, Plane plane, MotionEvent motionEvent) -> {
+ if (renderable_models[spn_model.getSelectedItemPosition()] == null) {
+ return;
+ }
+
+ distance_x=0f;
+ distance_z=0f;
+ myangle=0f;
+
+ myhit = hitResult;
+
+ // Create the Anchor.
+ Anchor anchor = hitResult.createAnchor();
+
+ AnchorNode anchorNode = new AnchorNode(anchor);
+
+
+ anchorNode.setParent(arFragment.getArSceneView().getScene());
+ anchorNodes.add(anchorNode);
+ sb_size.setEnabled(true);
+
+ myanchornode = anchorNode;
+
+ // Create the transformable andy and add it to the anchor.
+ TransformableNode andy;
+ if(mytranode == null)
+ andy = new TransformableNode(arFragment.getTransformationSystem());
+ else andy = mytranode;
+
+ andy.setParent(anchorNode);
+ andy.setRenderable(renderable_models[spn_model.getSelectedItemPosition()]);
+ andy.select();
+
+ mytranode = andy;
+ mytranode.setLocalRotation(new Quaternion(0f, 0f, 0f, 1f));
+ myanchornode.setLocalScale(new Vector3(mySize/70f, mySize/70f, mySize/70f));
+ });
+
+ ArrayAdapter adapter = new ArrayAdapter(ARSimulation.this,
+ android.R.layout.simple_spinner_dropdown_item,arr_models);
+
+ spn_model.setAdapter(adapter);
+ spn_model.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView> adapterView, View view, int i, long l) {
+ if(mytranode != null)
+ mytranode.setRenderable(renderable_models[i]);
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView> adapterView) {
+ }
+ });
+
+ }
+
+ void ascend(AnchorNode an, float x, float y, float z){
+ Anchor anchor = myhit.getTrackable().createAnchor(
+ myhit.getHitPose().compose(Pose.makeTranslation(x/100f, z/100f, y/100f)));
+
+ an.setAnchor(anchor);
+ }
+
+ Quaternion rotate(AnchorNode an, float angle) {
+ //mytranode.setLocalRotation(Quaternion.axisAngle(new Vector3(0, 1f, 0), angle));
+
+ return mytranode.getLocalRotation();
+ }
+
+
+ public float set(Quaternion q1) {
+ Vector3 angles = new Vector3();
+ double sqw = q1.w*q1.w;
+ double sqx = q1.x*q1.x;
+ double sqy = q1.y*q1.y;
+ double sqz = q1.z*q1.z;
+ double unit = sqx + sqy + sqz + sqw; // if normalised is one, otherwise is correction factor
+ double test = q1.x*q1.y + q1.z*q1.w;
+ if (test > 0.499*unit) { // singularity at north pole
+ angles.x = (float) (2 * atan2(q1.x,q1.w));
+ angles.y = (float) Math.PI/2;
+ angles.z = 0;
+ return angles.x;
+ }
+ if (test < -0.499*unit) { // singularity at south pole
+ angles.x = (float) (-2 * atan2(q1.x,q1.w));
+ angles.y = (float) (-Math.PI/2);
+ angles.z = 0;
+ return angles.x;
+ }
+ angles.x = (float) atan2(2*q1.y*q1.w-2*q1.x*q1.z , sqx - sqy - sqz + sqw);
+ angles.y = (float) Math.asin(2*test/unit);
+ angles.z = (float) atan2(2*q1.x*q1.w-2*q1.y*q1.z , -sqx + sqy - sqz + sqw);
+ return angles.x;
+ }
+
+
+ void forward(AnchorNode an){
+ distance_x+= Math.sin(myangle)*mytravel;
+ distance_z+= Math.cos(myangle)*mytravel;
+
+ Anchor anchor = myhit.getTrackable().createAnchor(
+ myhit.getHitPose().compose(Pose.makeTranslation(-distance_x, 0f, -distance_z)));
+
+ an.setAnchor(anchor);
+ }
+
+ float getMetersBetweenAnchors(Anchor anchor1, Anchor anchor2) {
+ float[] distance_vector = anchor1.getPose().inverse()
+ .compose(anchor2.getPose()).getTranslation();
+ float totalDistanceSquared = 0;
+ for(int i=0; i<3; ++i)
+ totalDistanceSquared += distance_vector[i]*distance_vector[i];
+ return (float) Math.sqrt(totalDistanceSquared);
+ }
+
+ public static boolean checkIsSupportedDeviceOrFinish(final Activity activity) {
+ if (Build.VERSION.SDK_INT < VERSION_CODES.N) {
+ Log.e(TAG, "Sceneform requires Android N or later");
+ Toast.makeText(activity, "Sceneform requires Android N or later", Toast.LENGTH_LONG).show();
+ activity.finish();
+ return false;
+ }
+ String openGlVersionString =
+ ((ActivityManager) activity.getSystemService(Context.ACTIVITY_SERVICE))
+ .getDeviceConfigurationInfo()
+ .getGlEsVersion();
+ if (Double.parseDouble(openGlVersionString) < MIN_OPENGL_VERSION) {
+ Log.e(TAG, "Sceneform requires OpenGL ES 3.0 later");
+ Toast.makeText(activity, "Sceneform requires OpenGL ES 3.0 or later", Toast.LENGTH_LONG)
+ .show();
+ activity.finish();
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/app/src/main/java/Satellite/DatabaseHandler.java b/app/src/main/java/Satellite/DatabaseHandler.java
new file mode 100644
index 0000000..fb05987
--- /dev/null
+++ b/app/src/main/java/Satellite/DatabaseHandler.java
@@ -0,0 +1,1087 @@
+
+package Satellite;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.location.Location;
+import android.util.Log;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.TimeZone;
+
+
+class DatabaseHandler extends SQLiteOpenHelper {
+
+ // All Static variables
+ // Database Version
+ private static final int DATABASE_VERSION = 2; // Updated to 2 in v2.1.3 (code 14)
+ private static final int LOCATION_TYPE_LOCATION = 1;
+ private static final int LOCATION_TYPE_PLACEMARK = 2;
+
+ // Database Name
+ private static final String DATABASE_NAME = "GPS";
+
+ // -------------------------------------------------------------------------------- Table names
+ private static final String TABLE_LOCATIONS = "locations";
+ private static final String TABLE_TRACKS = "tracks";
+ private static final String TABLE_PLACEMARKS = "placemarks";
+
+ // ----------------------------------------------------------------------- Common Columns names
+ private static final String KEY_ID = "id";
+ private static final String KEY_TRACK_ID = "track_id";
+
+ // --------------------------------------------------------------- Location Table Columns names
+ private static final String KEY_LOCATION_NUMBER = "nr";
+ private static final String KEY_LOCATION_LATITUDE = "latitude";
+ private static final String KEY_LOCATION_LONGITUDE = "longitude";
+ private static final String KEY_LOCATION_ALTITUDE = "altitude";
+ private static final String KEY_LOCATION_SPEED = "speed";
+ private static final String KEY_LOCATION_ACCURACY = "accuracy";
+ private static final String KEY_LOCATION_BEARING = "bearing";
+ private static final String KEY_LOCATION_TIME = "time";
+ private static final String KEY_LOCATION_NUMBEROFSATELLITES = "number_of_satellites";
+ private static final String KEY_LOCATION_TYPE = "type";
+ private static final String KEY_LOCATION_NUMBEROFSATELLITESUSEDINFIX = "number_of_satellites_used_in_fix";
+
+ // ---------------------------------------------------------------------------- Placemarks adds
+ private static final String KEY_LOCATION_NAME = "name";
+
+ // ------------------------------------------------------------------ Track Table Columns names
+ private static final String KEY_TRACK_NAME = "name";
+ private static final String KEY_TRACK_FROM = "location_from";
+ private static final String KEY_TRACK_TO = "location_to";
+
+
+ private static final String KEY_TRACK_START_LATITUDE = "start_latitude";
+ private static final String KEY_TRACK_START_LONGITUDE = "start_longitude";
+ private static final String KEY_TRACK_START_ALTITUDE = "start_altitude";
+ private static final String KEY_TRACK_START_ACCURACY = "start_accuracy";
+ private static final String KEY_TRACK_START_SPEED = "start_speed";
+ private static final String KEY_TRACK_START_TIME = "start_time";
+
+ private static final String KEY_TRACK_LASTFIX_TIME = "lastfix_time";
+
+ private static final String KEY_TRACK_END_LATITUDE = "end_latitude";
+ private static final String KEY_TRACK_END_LONGITUDE = "end_longitude";
+ private static final String KEY_TRACK_END_ALTITUDE = "end_altitude";
+ private static final String KEY_TRACK_END_ACCURACY = "end_accuracy";
+ private static final String KEY_TRACK_END_SPEED = "end_speed";
+ private static final String KEY_TRACK_END_TIME = "end_time";
+
+ private static final String KEY_TRACK_LASTSTEPDST_LATITUDE = "laststepdst_latitude";
+ private static final String KEY_TRACK_LASTSTEPDST_LONGITUDE = "laststepdst_longitude";
+ private static final String KEY_TRACK_LASTSTEPDST_ACCURACY = "laststepdst_accuracy";
+
+ private static final String KEY_TRACK_LASTSTEPALT_ALTITUDE = "laststepalt_altitude";
+ private static final String KEY_TRACK_LASTSTEPALT_ACCURACY = "laststepalt_accuracy";
+
+ private static final String KEY_TRACK_MIN_LATITUDE = "min_latitude";
+ private static final String KEY_TRACK_MIN_LONGITUDE = "min_longitude";
+
+ private static final String KEY_TRACK_MAX_LATITUDE = "max_latitude";
+ private static final String KEY_TRACK_MAX_LONGITUDE = "max_longitude";
+
+ private static final String KEY_TRACK_DURATION = "duration";
+ private static final String KEY_TRACK_DURATION_MOVING = "duration_moving";
+
+ private static final String KEY_TRACK_DISTANCE = "distance";
+ private static final String KEY_TRACK_DISTANCE_INPROGRESS = "distance_in_progress";
+ private static final String KEY_TRACK_DISTANCE_LASTALTITUDE = "distance_last_altitude";
+
+ private static final String KEY_TRACK_ALTITUDE_UP = "altitude_up";
+ private static final String KEY_TRACK_ALTITUDE_DOWN = "altitude_down";
+ private static final String KEY_TRACK_ALTITUDE_INPROGRESS = "altitude_in_progress";
+
+ private static final String KEY_TRACK_SPEED_MAX = "speed_max";
+ private static final String KEY_TRACK_SPEED_AVERAGE = "speed_average";
+ private static final String KEY_TRACK_SPEED_AVERAGEMOVING = "speed_average_moving";
+
+ private static final String KEY_TRACK_NUMBEROFLOCATIONS = "number_of_locations";
+ private static final String KEY_TRACK_NUMBEROFPLACEMARKS = "number_of_placemarks";
+ private static final String KEY_TRACK_TYPE = "type";
+
+ private static final String KEY_TRACK_VALIDMAP = "validmap";
+
+
+
+ public DatabaseHandler(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+
+ // Creating Tables
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ String CREATE_TRACKS_TABLE = "CREATE TABLE " + TABLE_TRACKS + "("
+ + KEY_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," // 0
+ + KEY_TRACK_NAME + " TEXT," // 1
+ + KEY_TRACK_FROM + " TEXT," // 2
+ + KEY_TRACK_TO + " TEXT," // 3
+ + KEY_TRACK_START_LATITUDE + " REAL," // 4
+ + KEY_TRACK_START_LONGITUDE + " REAL," // 5
+ + KEY_TRACK_START_ALTITUDE + " REAL," // 6
+ + KEY_TRACK_START_ACCURACY + " REAL," // 7
+ + KEY_TRACK_START_SPEED + " REAL," // 8
+ + KEY_TRACK_START_TIME + " REAL," // 9
+ + KEY_TRACK_LASTFIX_TIME + " REAL," // 10
+ + KEY_TRACK_END_LATITUDE + " REAL," // 11
+ + KEY_TRACK_END_LONGITUDE + " REAL," // 12
+ + KEY_TRACK_END_ALTITUDE + " REAL," // 13
+ + KEY_TRACK_END_ACCURACY + " REAL," // 14
+ + KEY_TRACK_END_SPEED + " REAL," // 15
+ + KEY_TRACK_END_TIME + " REAL," // 16
+ + KEY_TRACK_LASTSTEPDST_LATITUDE + " REAL," // 17
+ + KEY_TRACK_LASTSTEPDST_LONGITUDE + " REAL," // 18
+ + KEY_TRACK_LASTSTEPDST_ACCURACY + " REAL," // 19
+ + KEY_TRACK_LASTSTEPALT_ALTITUDE + " REAL," // 20
+ + KEY_TRACK_LASTSTEPALT_ACCURACY + " REAL," // 21
+ + KEY_TRACK_MIN_LATITUDE + " REAL," // 22
+ + KEY_TRACK_MIN_LONGITUDE + " REAL," // 23
+ + KEY_TRACK_MAX_LATITUDE + " REAL," // 24
+ + KEY_TRACK_MAX_LONGITUDE + " REAL," // 25
+ + KEY_TRACK_DURATION + " REAL," // 26
+ + KEY_TRACK_DURATION_MOVING + " REAL," // 27
+ + KEY_TRACK_DISTANCE + " REAL," // 28
+ + KEY_TRACK_DISTANCE_INPROGRESS + " REAL," // 29
+ + KEY_TRACK_DISTANCE_LASTALTITUDE + " REAL," // 30
+ + KEY_TRACK_ALTITUDE_UP + " REAL," // 31
+ + KEY_TRACK_ALTITUDE_DOWN + " REAL," // 32
+ + KEY_TRACK_ALTITUDE_INPROGRESS + " REAL," // 33
+ + KEY_TRACK_SPEED_MAX + " REAL," // 34
+ + KEY_TRACK_SPEED_AVERAGE + " REAL," // 35
+ + KEY_TRACK_SPEED_AVERAGEMOVING + " REAL," // 36
+ + KEY_TRACK_NUMBEROFLOCATIONS + " INTEGER," // 37
+ + KEY_TRACK_NUMBEROFPLACEMARKS + " INTEGER," // 38
+ + KEY_TRACK_VALIDMAP + " INTEGER," // 39
+ + KEY_TRACK_TYPE + " INTEGER " + ")"; // 40
+ db.execSQL(CREATE_TRACKS_TABLE);
+
+ String CREATE_LOCATIONS_TABLE = "CREATE TABLE " + TABLE_LOCATIONS + "("
+ + KEY_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," // 0
+ + KEY_TRACK_ID + " INTEGER," // 1
+ + KEY_LOCATION_NUMBER + " INTEGER," // 2
+ + KEY_LOCATION_LATITUDE + " REAL," // 3
+ + KEY_LOCATION_LONGITUDE + " REAL," // 4
+ + KEY_LOCATION_ALTITUDE + " REAL," // 5
+ + KEY_LOCATION_SPEED + " REAL," // 6
+ + KEY_LOCATION_ACCURACY + " REAL," // 7
+ + KEY_LOCATION_BEARING + " REAL," // 8
+ + KEY_LOCATION_TIME + " REAL," // 9
+ + KEY_LOCATION_NUMBEROFSATELLITES + " INTEGER," // 10
+ + KEY_LOCATION_TYPE + " INTEGER," // 11
+ + KEY_LOCATION_NUMBEROFSATELLITESUSEDINFIX + " INTEGER" + ")"; // 12
+ db.execSQL(CREATE_LOCATIONS_TABLE);
+
+ String CREATE_PLACEMARKS_TABLE = "CREATE TABLE " + TABLE_PLACEMARKS + "("
+ + KEY_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," // 0
+ + KEY_TRACK_ID + " INTEGER," // 1
+ + KEY_LOCATION_NUMBER + " INTEGER," // 2
+ + KEY_LOCATION_LATITUDE + " REAL," // 3
+ + KEY_LOCATION_LONGITUDE + " REAL," // 4
+ + KEY_LOCATION_ALTITUDE + " REAL," // 5
+ + KEY_LOCATION_SPEED + " REAL," // 6
+ + KEY_LOCATION_ACCURACY + " REAL," // 7
+ + KEY_LOCATION_BEARING + " REAL," // 8
+ + KEY_LOCATION_TIME + " REAL," // 9
+ + KEY_LOCATION_NUMBEROFSATELLITES + " INTEGER," // 10
+ + KEY_LOCATION_TYPE + " INTEGER," // 11
+ + KEY_LOCATION_NAME + " TEXT," // 12
+ + KEY_LOCATION_NUMBEROFSATELLITESUSEDINFIX + " INTEGER" + ")"; // 13
+ db.execSQL(CREATE_PLACEMARKS_TABLE);
+ }
+
+
+ private static final int NOT_AVAILABLE = -100000;
+
+ private static final String DATABASE_ALTER_TABLE_LOCATIONS_TO_V2 = "ALTER TABLE "
+ + TABLE_LOCATIONS + " ADD COLUMN " + KEY_LOCATION_NUMBEROFSATELLITESUSEDINFIX + " INTEGER DEFAULT " + NOT_AVAILABLE + ";";
+ private static final String DATABASE_ALTER_TABLE_PLACEMARKS_TO_V2 = "ALTER TABLE "
+ + TABLE_PLACEMARKS + " ADD COLUMN " + KEY_LOCATION_NUMBEROFSATELLITESUSEDINFIX + " INTEGER DEFAULT " + NOT_AVAILABLE + ";";
+
+ // Upgrading database
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+
+ // Use this function in case of DB version upgrade.
+
+ // Drop older table if existed
+ //db.execSQL("DROP TABLE IF EXISTS " + TABLE_LOCATIONS);
+ //db.execSQL("DROP TABLE IF EXISTS " + TABLE_PLACEMARKS);
+ //db.execSQL("DROP TABLE IF EXISTS " + TABLE_TRACKS);
+
+ // Create tables again
+ //onCreate(db);
+
+ switch (oldVersion)
+ {
+ case 1:
+ //upgrade from version 1 to 2
+ //Log.w("myApp", "[#] DatabaseHandler.java - onUpgrade: from version 1 to 2 ...");
+ db.execSQL(DATABASE_ALTER_TABLE_LOCATIONS_TO_V2);
+ db.execSQL(DATABASE_ALTER_TABLE_PLACEMARKS_TO_V2);
+ //case 2:
+ //upgrade from version 2 to 3
+ // db.execSQL(DATABASE_ALTER_TEAM_TO_V3);
+
+ //and so on.. do not add breaks so that switch will
+ //start at oldVersion, and run straight through to the latest
+ }
+ //Log.w("myApp", "[#] DatabaseHandler.java - onUpgrade: DB upgraded to version " + newVersion);
+ }
+
+// ----------------------------------------------------------------------- LOCATIONS AND PLACEMARKS
+
+ // Add new Location and update the corresponding track
+ public void addLocationToTrack(LocationExtended location, Track track) {
+ SQLiteDatabase db = this.getWritableDatabase();
+
+ Location loc = location.getLocation();
+
+ ContentValues locvalues = new ContentValues();
+ locvalues.put(KEY_TRACK_ID, track.getId());
+ locvalues.put(KEY_LOCATION_NUMBER, track.getNumberOfLocations());
+ locvalues.put(KEY_LOCATION_LATITUDE, loc.getLatitude());
+ locvalues.put(KEY_LOCATION_LONGITUDE, loc.getLongitude());
+ locvalues.put(KEY_LOCATION_ALTITUDE, loc.hasAltitude() ? loc.getAltitude() : NOT_AVAILABLE);
+ locvalues.put(KEY_LOCATION_SPEED, loc.hasSpeed() ? loc.getSpeed() : NOT_AVAILABLE);
+ locvalues.put(KEY_LOCATION_ACCURACY, loc.hasAccuracy() ? loc.getAccuracy() : NOT_AVAILABLE);
+ locvalues.put(KEY_LOCATION_BEARING, loc.hasBearing() ? loc.getBearing() : NOT_AVAILABLE);
+ locvalues.put(KEY_LOCATION_TIME, loc.getTime());
+ locvalues.put(KEY_LOCATION_NUMBEROFSATELLITES, location.getNumberOfSatellites());
+ locvalues.put(KEY_LOCATION_TYPE, LOCATION_TYPE_LOCATION);
+ locvalues.put(KEY_LOCATION_NUMBEROFSATELLITESUSEDINFIX, location.getNumberOfSatellitesUsedInFix());
+
+ ContentValues trkvalues = new ContentValues();
+ trkvalues.put(KEY_TRACK_NAME, track.getName());
+ trkvalues.put(KEY_TRACK_FROM, "");
+ trkvalues.put(KEY_TRACK_TO, "");
+
+ trkvalues.put(KEY_TRACK_START_LATITUDE, track.getStart_Latitude());
+ trkvalues.put(KEY_TRACK_START_LONGITUDE, track.getStart_Longitude());
+ trkvalues.put(KEY_TRACK_START_ALTITUDE, track.getStart_Altitude());
+ trkvalues.put(KEY_TRACK_START_ACCURACY, track.getStart_Accuracy());
+ trkvalues.put(KEY_TRACK_START_SPEED, track.getStart_Speed());
+ trkvalues.put(KEY_TRACK_START_TIME, track.getStart_Time());
+
+ trkvalues.put(KEY_TRACK_LASTFIX_TIME, track.getLastFix_Time());
+
+ trkvalues.put(KEY_TRACK_END_LATITUDE, track.getEnd_Latitude());
+ trkvalues.put(KEY_TRACK_END_LONGITUDE, track.getEnd_Longitude());
+ trkvalues.put(KEY_TRACK_END_ALTITUDE, track.getEnd_Altitude());
+ trkvalues.put(KEY_TRACK_END_ACCURACY, track.getEnd_Accuracy());
+ trkvalues.put(KEY_TRACK_END_SPEED, track.getEnd_Speed());
+ trkvalues.put(KEY_TRACK_END_TIME, track.getEnd_Time());
+
+ trkvalues.put(KEY_TRACK_LASTSTEPDST_LATITUDE, track.getLastStepDistance_Latitude());
+ trkvalues.put(KEY_TRACK_LASTSTEPDST_LONGITUDE, track.getLastStepDistance_Longitude());
+ trkvalues.put(KEY_TRACK_LASTSTEPDST_ACCURACY, track.getLastStepDistance_Accuracy());
+
+ trkvalues.put(KEY_TRACK_LASTSTEPALT_ALTITUDE, track.getLastStepAltitude_Altitude());
+ trkvalues.put(KEY_TRACK_LASTSTEPALT_ACCURACY, track.getLastStepAltitude_Accuracy());
+
+ trkvalues.put(KEY_TRACK_MIN_LATITUDE, track.getMin_Latitude());
+ trkvalues.put(KEY_TRACK_MIN_LONGITUDE, track.getMin_Longitude());
+
+ trkvalues.put(KEY_TRACK_MAX_LATITUDE, track.getMax_Latitude());
+ trkvalues.put(KEY_TRACK_MAX_LONGITUDE, track.getMax_Longitude());
+
+ trkvalues.put(KEY_TRACK_DURATION, track.getDuration());
+ trkvalues.put(KEY_TRACK_DURATION_MOVING, track.getDuration_Moving());
+
+ trkvalues.put(KEY_TRACK_DISTANCE, track.getDistance());
+ trkvalues.put(KEY_TRACK_DISTANCE_INPROGRESS, track.getDistanceInProgress());
+ trkvalues.put(KEY_TRACK_DISTANCE_LASTALTITUDE, track.getDistanceLastAltitude());
+
+ trkvalues.put(KEY_TRACK_ALTITUDE_UP, track.getAltitude_Up());
+ trkvalues.put(KEY_TRACK_ALTITUDE_DOWN, track.getAltitude_Down());
+ trkvalues.put(KEY_TRACK_ALTITUDE_INPROGRESS, track.getAltitude_InProgress());
+
+ trkvalues.put(KEY_TRACK_SPEED_MAX, track.getSpeedMax());
+ trkvalues.put(KEY_TRACK_SPEED_AVERAGE, track.getSpeedAverage());
+ trkvalues.put(KEY_TRACK_SPEED_AVERAGEMOVING, track.getSpeedAverageMoving());
+
+ trkvalues.put(KEY_TRACK_NUMBEROFLOCATIONS, track.getNumberOfLocations());
+ trkvalues.put(KEY_TRACK_NUMBEROFPLACEMARKS, track.getNumberOfPlacemarks());
+ trkvalues.put(KEY_TRACK_TYPE, track.getType());
+
+ trkvalues.put(KEY_TRACK_VALIDMAP, track.getValidMap());
+
+ db.beginTransaction();
+ db.insert(TABLE_LOCATIONS, null, locvalues); // Insert the new Location
+ db.update(TABLE_TRACKS, trkvalues, KEY_ID + " = ?",
+ new String[] { String.valueOf(track.getId()) }); // Update the corresponding Track
+ db.setTransactionSuccessful();
+ db.endTransaction();
+
+ //Log.w("myApp", "[#] DatabaseHandler.java - addLocation: Location " + track.getNumberOfLocations() + " added into track " + track.getID());
+ }
+
+
+ // Add new Placemark and update the corresponding track
+ public void addPlacemarkToTrack(LocationExtended placemark, Track track) {
+ SQLiteDatabase db = this.getWritableDatabase();
+
+ Location loc = placemark.getLocation();
+
+ ContentValues locvalues = new ContentValues();
+ locvalues.put(KEY_TRACK_ID, track.getId());
+ locvalues.put(KEY_LOCATION_NUMBER, track.getNumberOfPlacemarks());
+ locvalues.put(KEY_LOCATION_LATITUDE, loc.getLatitude());
+ locvalues.put(KEY_LOCATION_LONGITUDE, loc.getLongitude());
+ locvalues.put(KEY_LOCATION_ALTITUDE, loc.hasAltitude() ? loc.getAltitude() : NOT_AVAILABLE);
+ locvalues.put(KEY_LOCATION_SPEED, loc.hasSpeed() ? loc.getSpeed() : NOT_AVAILABLE);
+ locvalues.put(KEY_LOCATION_ACCURACY, loc.hasAccuracy() ? loc.getAccuracy() : NOT_AVAILABLE);
+ locvalues.put(KEY_LOCATION_BEARING, loc.hasBearing() ? loc.getBearing() : NOT_AVAILABLE);
+ locvalues.put(KEY_LOCATION_TIME, loc.getTime());
+ locvalues.put(KEY_LOCATION_NUMBEROFSATELLITES, placemark.getNumberOfSatellites());
+ locvalues.put(KEY_LOCATION_TYPE, LOCATION_TYPE_PLACEMARK);
+ locvalues.put(KEY_LOCATION_NAME, placemark.getDescription());
+ locvalues.put(KEY_LOCATION_NUMBEROFSATELLITESUSEDINFIX, placemark.getNumberOfSatellitesUsedInFix());
+
+ ContentValues trkvalues = new ContentValues();
+ trkvalues.put(KEY_TRACK_NAME, track.getName());
+ trkvalues.put(KEY_TRACK_FROM, "");
+ trkvalues.put(KEY_TRACK_TO, "");
+
+ trkvalues.put(KEY_TRACK_START_LATITUDE, track.getStart_Latitude());
+ trkvalues.put(KEY_TRACK_START_LONGITUDE, track.getStart_Longitude());
+ trkvalues.put(KEY_TRACK_START_ALTITUDE, track.getStart_Altitude());
+ trkvalues.put(KEY_TRACK_START_ACCURACY, track.getStart_Accuracy());
+ trkvalues.put(KEY_TRACK_START_SPEED, track.getStart_Speed());
+ trkvalues.put(KEY_TRACK_START_TIME, track.getStart_Time());
+
+ trkvalues.put(KEY_TRACK_LASTFIX_TIME, track.getLastFix_Time());
+
+ trkvalues.put(KEY_TRACK_END_LATITUDE, track.getEnd_Latitude());
+ trkvalues.put(KEY_TRACK_END_LONGITUDE, track.getEnd_Longitude());
+ trkvalues.put(KEY_TRACK_END_ALTITUDE, track.getEnd_Altitude());
+ trkvalues.put(KEY_TRACK_END_ACCURACY, track.getEnd_Accuracy());
+ trkvalues.put(KEY_TRACK_END_SPEED, track.getEnd_Speed());
+ trkvalues.put(KEY_TRACK_END_TIME, track.getEnd_Time());
+
+ trkvalues.put(KEY_TRACK_LASTSTEPDST_LATITUDE, track.getLastStepDistance_Latitude());
+ trkvalues.put(KEY_TRACK_LASTSTEPDST_LONGITUDE, track.getLastStepDistance_Longitude());
+ trkvalues.put(KEY_TRACK_LASTSTEPDST_ACCURACY, track.getLastStepDistance_Accuracy());
+
+ trkvalues.put(KEY_TRACK_LASTSTEPALT_ALTITUDE, track.getLastStepAltitude_Altitude());
+ trkvalues.put(KEY_TRACK_LASTSTEPALT_ACCURACY, track.getLastStepAltitude_Accuracy());
+
+ trkvalues.put(KEY_TRACK_MIN_LATITUDE, track.getMin_Latitude());
+ trkvalues.put(KEY_TRACK_MIN_LONGITUDE, track.getMin_Longitude());
+
+ trkvalues.put(KEY_TRACK_MAX_LATITUDE, track.getMax_Latitude());
+ trkvalues.put(KEY_TRACK_MAX_LONGITUDE, track.getMax_Longitude());
+
+ trkvalues.put(KEY_TRACK_DURATION, track.getDuration());
+ trkvalues.put(KEY_TRACK_DURATION_MOVING, track.getDuration_Moving());
+
+ trkvalues.put(KEY_TRACK_DISTANCE, track.getDistance());
+ trkvalues.put(KEY_TRACK_DISTANCE_INPROGRESS, track.getDistanceInProgress());
+ trkvalues.put(KEY_TRACK_DISTANCE_LASTALTITUDE, track.getDistanceLastAltitude());
+
+ trkvalues.put(KEY_TRACK_ALTITUDE_UP, track.getAltitude_Up());
+ trkvalues.put(KEY_TRACK_ALTITUDE_DOWN, track.getAltitude_Down());
+ trkvalues.put(KEY_TRACK_ALTITUDE_INPROGRESS, track.getAltitude_InProgress());
+
+ trkvalues.put(KEY_TRACK_SPEED_MAX, track.getSpeedMax());
+ trkvalues.put(KEY_TRACK_SPEED_AVERAGE, track.getSpeedAverage());
+ trkvalues.put(KEY_TRACK_SPEED_AVERAGEMOVING, track.getSpeedAverageMoving());
+
+ trkvalues.put(KEY_TRACK_NUMBEROFLOCATIONS, track.getNumberOfLocations());
+ trkvalues.put(KEY_TRACK_NUMBEROFPLACEMARKS, track.getNumberOfPlacemarks());
+ trkvalues.put(KEY_TRACK_TYPE, track.getType());
+
+ trkvalues.put(KEY_TRACK_VALIDMAP, track.getValidMap());
+
+ db.beginTransaction();
+ db.insert(TABLE_PLACEMARKS, null, locvalues); // Insert the new Location
+ db.update(TABLE_TRACKS, trkvalues, KEY_ID + " = ?",
+ new String[] { String.valueOf(track.getId()) }); // Update the corresponding Track
+ db.setTransactionSuccessful();
+ db.endTransaction();
+
+ //Log.w("myApp", "[#] DatabaseHandler.java - addLocation: Location " + track.getNumberOfLocations() + " added into track " + track.getID());
+ }
+
+
+ // Get single Location
+ public LocationExtended getLocation(long id) {
+ SQLiteDatabase db = this.getWritableDatabase();
+ LocationExtended extdloc = null;
+ double lcdata_double;
+ float lcdata_float;
+
+ Cursor cursor = db.query(TABLE_LOCATIONS, new String[] {KEY_ID,
+ KEY_LOCATION_LATITUDE,
+ KEY_LOCATION_LONGITUDE,
+ KEY_LOCATION_ALTITUDE,
+ KEY_LOCATION_SPEED,
+ KEY_LOCATION_ACCURACY,
+ KEY_LOCATION_BEARING,
+ KEY_LOCATION_TIME,
+ KEY_LOCATION_NUMBEROFSATELLITES,
+ KEY_LOCATION_NUMBEROFSATELLITESUSEDINFIX}, KEY_ID + "=?",
+ new String[] { String.valueOf(id) }, null, null, null, null);
+ if (cursor != null) {
+ cursor.moveToFirst();
+
+ Location lc = new Location("DB");
+ lc.setLatitude(cursor.getDouble(1));
+ lc.setLongitude(cursor.getDouble(2));
+
+ lcdata_double = cursor.getDouble(3);
+ if (lcdata_double != NOT_AVAILABLE) lc.setAltitude(lcdata_double);
+ //else lc.removeAltitude();
+
+ lcdata_float = cursor.getFloat(4);
+ if (lcdata_float != NOT_AVAILABLE) lc.setSpeed(lcdata_float);
+ //else lc.removeSpeed();
+
+ lcdata_float = cursor.getFloat(5);
+ if (lcdata_float != NOT_AVAILABLE) lc.setAccuracy(lcdata_float);
+ //else lc.removeAccuracy();
+
+ lcdata_float = cursor.getFloat(6);
+ if (lcdata_float != NOT_AVAILABLE) lc.setBearing(lcdata_float);
+ //else lc.removeBearing();
+
+ lc.setTime(cursor.getLong(7));
+
+
+ extdloc = new LocationExtended(lc);
+ extdloc.setNumberOfSatellites(cursor.getInt(8));
+ extdloc.setNumberOfSatellitesUsedInFix(cursor.getInt(9));
+
+ cursor.close();
+ }
+ return extdloc != null ? extdloc : null;
+ }
+
+
+
+ // Getting a list of Locations associated to a specified track, with number between startNumber and endNumber
+ // Please note that limits both are inclusive!
+ public List getLocationsList(long TrackID, long startNumber, long endNumber) {
+
+ List locationList = new ArrayList<>();
+
+ String selectQuery = "SELECT * FROM " + TABLE_LOCATIONS + " WHERE "
+ + KEY_TRACK_ID + " = " + TrackID + " AND "
+ + KEY_LOCATION_NUMBER + " BETWEEN " + startNumber + " AND " + endNumber
+ + " ORDER BY " + KEY_LOCATION_NUMBER;
+
+ //Log.w("myApp", "[#] DatabaseHandler.java - getLocationList(" + TrackID + ", " + startNumber + ", " +endNumber + ") ==> " + selectQuery);
+
+ SQLiteDatabase db = this.getWritableDatabase();
+ Cursor cursor = db.rawQuery(selectQuery, null);
+ double lcdata_double;
+ float lcdata_float;
+
+ if (cursor != null) {
+ // looping through all rows and adding to list
+ if (cursor.moveToFirst()) {
+ do {
+ Location lc = new Location("DB");
+ lc.setLatitude(cursor.getDouble(3));
+ lc.setLongitude(cursor.getDouble(4));
+
+ lcdata_double = cursor.getDouble(5);
+ if (lcdata_double != NOT_AVAILABLE) lc.setAltitude(lcdata_double);
+ //else lc.removeAltitude();
+
+ lcdata_float = cursor.getFloat(6);
+ if (lcdata_float != NOT_AVAILABLE) lc.setSpeed(lcdata_float);
+ //else lc.removeSpeed();
+
+ lcdata_float = cursor.getFloat(7);
+ if (lcdata_float != NOT_AVAILABLE) lc.setAccuracy(lcdata_float);
+ //else lc.removeAccuracy();
+
+ lcdata_float = cursor.getFloat(8);
+ if (lcdata_float != NOT_AVAILABLE) lc.setBearing(lcdata_float);
+ //else lc.removeBearing();
+
+ lc.setTime(cursor.getLong(9));
+
+ LocationExtended extdloc = new LocationExtended(lc);
+ extdloc.setNumberOfSatellites(cursor.getInt(10));
+ extdloc.setNumberOfSatellitesUsedInFix(cursor.getInt(12));
+
+ locationList.add(extdloc); // Add Location to list
+ } while (cursor.moveToNext());
+ }
+ cursor.close();
+ }
+ return locationList;
+ }
+
+ // Getting a list of Locations associated to a specified track, with number between startNumber and endNumber
+ // Please note that limits both are inclusive!
+ public List getPlacemarksList(long TrackID, long startNumber, long endNumber) {
+
+ List placemarkList = new ArrayList<>();
+
+ String selectQuery = "SELECT * FROM " + TABLE_PLACEMARKS + " WHERE "
+ + KEY_TRACK_ID + " = " + TrackID + " AND "
+ + KEY_LOCATION_NUMBER + " BETWEEN " + startNumber + " AND " + endNumber
+ + " ORDER BY " + KEY_LOCATION_NUMBER;
+
+ //Log.w("myApp", "[#] DatabaseHandler.java - getLocationList(" + TrackID + ", " + startNumber + ", " +endNumber + ") ==> " + selectQuery);
+
+ SQLiteDatabase db = this.getWritableDatabase();
+ Cursor cursor = db.rawQuery(selectQuery, null);
+ double lcdata_double;
+ float lcdata_float;
+
+ if (cursor != null) {
+ // looping through all rows and adding to list
+ if (cursor.moveToFirst()) {
+ do {
+ Location lc = new Location("DB");
+ lc.setLatitude(cursor.getDouble(3));
+ lc.setLongitude(cursor.getDouble(4));
+
+ lcdata_double = cursor.getDouble(5);
+ if (lcdata_double != NOT_AVAILABLE) lc.setAltitude(lcdata_double);
+ //else lc.removeAltitude();
+
+ lcdata_float = cursor.getFloat(6);
+ if (lcdata_float != NOT_AVAILABLE) lc.setSpeed(lcdata_float);
+ //else lc.removeSpeed();
+
+ lcdata_float = cursor.getFloat(7);
+ if (lcdata_float != NOT_AVAILABLE) lc.setAccuracy(lcdata_float);
+ //else lc.removeAccuracy();
+
+ lcdata_float = cursor.getFloat(8);
+ if (lcdata_float != NOT_AVAILABLE) lc.setBearing(lcdata_float);
+ //else lc.removeBearing();
+
+ lc.setTime(cursor.getLong(9));
+
+ LocationExtended extdloc = new LocationExtended(lc);
+ extdloc.setNumberOfSatellites(cursor.getInt(10));
+ extdloc.setNumberOfSatellitesUsedInFix(cursor.getInt(13));
+ extdloc.setDescription(cursor.getString(12));
+
+ placemarkList.add(extdloc); // Add Location to list
+ } while (cursor.moveToNext());
+ }
+ cursor.close();
+ }
+ return placemarkList;
+ }
+
+
+ // Getting a list of Locations associated to a specified track, with number between startNumber and endNumber
+ // Please note that limits both are inclusive!
+ public List getLatLngList(long TrackID, long startNumber, long endNumber) {
+
+ List latlngList = new ArrayList<>();
+
+ String selectQuery = "SELECT " + KEY_TRACK_ID + "," + KEY_LOCATION_LATITUDE + "," + KEY_LOCATION_LONGITUDE + "," + KEY_LOCATION_NUMBER
+ + " FROM " + TABLE_LOCATIONS + " WHERE "
+ + KEY_TRACK_ID + " = " + TrackID + " AND "
+ + KEY_LOCATION_NUMBER + " BETWEEN " + startNumber + " AND " + endNumber
+ + " ORDER BY " + KEY_LOCATION_NUMBER;
+
+ //Log.w("myApp", "[#] DatabaseHandler.java - getLocationList(" + TrackID + ", " + startNumber + ", " +endNumber + ") ==> " + selectQuery);
+
+ SQLiteDatabase db = this.getWritableDatabase();
+ Cursor cursor = db.rawQuery(selectQuery, null);
+
+ if (cursor != null) {
+ // looping through all rows and adding to list
+ if (cursor.moveToFirst()) {
+ do {
+ LatLng latlng = new LatLng();
+ latlng.Latitude = cursor.getDouble(1);
+ latlng.Longitude = cursor.getDouble(2);
+
+ latlngList.add(latlng); // Add Location to list
+ } while (cursor.moveToNext());
+ }
+ cursor.close();
+ }
+ return latlngList;
+ }
+
+
+ // Get the total amount of Locations stored in the DB
+ public long getLocationsTotalCount() {
+ String countQuery = "SELECT * FROM " + TABLE_LOCATIONS;
+ SQLiteDatabase db = this.getWritableDatabase();
+ Cursor cursor = db.rawQuery(countQuery, null);
+ long result = cursor.getCount();
+ cursor.close();
+ return result;
+ }
+
+
+ // Get the number of Locations of a Track
+ public long getLocationsCount(long TrackID) {
+ String countQuery = "SELECT * FROM " + TABLE_LOCATIONS + " WHERE " + KEY_TRACK_ID + " = " + TrackID;
+ SQLiteDatabase db = this.getWritableDatabase();
+ Cursor cursor = db.rawQuery(countQuery, null);
+ long result = cursor.getCount();
+ cursor.close();
+ return result;
+ }
+
+
+ // Get last Location ID
+ public long getLastLocationID(long TrackID) {
+ SQLiteDatabase db = this.getWritableDatabase();
+ long result = 0;
+
+ String query = "SELECT " + KEY_ID + " FROM " + TABLE_LOCATIONS +
+ " WHERE " + KEY_TRACK_ID + " = " + TrackID + " ORDER BY " + KEY_ID + " DESC LIMIT 1";
+
+ //Log.w("myApp", "[#] DatabaseHandler.java - getLastLocationID(): " + query);
+
+ Cursor cursor = db.rawQuery(query, null);
+
+ if (cursor != null) {
+ //Log.w("myApp", "[#] !null");
+ if (cursor.moveToFirst()) {
+ result = cursor.getLong(0);
+ } //else Log.w("myApp", "[#] DatabaseHandler.java - Location table is empty");
+ cursor.close();
+ }
+ //Log.w("myApp", "[#] RETURN getLastTrack()");
+ return result;
+ }
+
+
+
+
+// ----------------------------------------------------------------------------------------- TRACKS
+
+ // Delete the track with the specified ID;
+ // The method deletes also Placemarks and Locations associated to the specified track
+ public void DeleteTrack(long TrackID) {
+ SQLiteDatabase db = this.getWritableDatabase();
+ db.beginTransaction();
+ db.delete(TABLE_PLACEMARKS, KEY_TRACK_ID + " = ?",
+ new String[] { String.valueOf(TrackID) }); // Delete track's Placemarks
+ db.delete(TABLE_LOCATIONS, KEY_TRACK_ID + " = ?",
+ new String[] { String.valueOf(TrackID) }); // Delete track's Locations
+ db.delete(TABLE_TRACKS, KEY_ID + " = ?",
+ new String[] { String.valueOf(TrackID) }); // Delete track
+ db.setTransactionSuccessful();
+ db.endTransaction();
+
+ //Log.w("myApp", "[#] DatabaseHandler.java - addLocation: Location " + track.getNumberOfLocations() + " added into track " + track.getID());
+ }
+
+
+ // Add a new Track, returns the TrackID
+ public long addTrack(Track track) {
+
+ SQLiteDatabase db = this.getWritableDatabase();
+
+ ContentValues trkvalues = new ContentValues();
+ trkvalues.put(KEY_TRACK_NAME, track.getName());
+ trkvalues.put(KEY_TRACK_FROM, "");
+ trkvalues.put(KEY_TRACK_TO, "");
+
+ trkvalues.put(KEY_TRACK_START_LATITUDE, track.getStart_Latitude());
+ trkvalues.put(KEY_TRACK_START_LONGITUDE, track.getStart_Longitude());
+ trkvalues.put(KEY_TRACK_START_ALTITUDE, track.getStart_Altitude());
+ trkvalues.put(KEY_TRACK_START_ACCURACY, track.getStart_Accuracy());
+ trkvalues.put(KEY_TRACK_START_SPEED, track.getStart_Speed());
+ trkvalues.put(KEY_TRACK_START_TIME, track.getStart_Time());
+
+ trkvalues.put(KEY_TRACK_LASTFIX_TIME, track.getLastFix_Time());
+
+ trkvalues.put(KEY_TRACK_END_LATITUDE, track.getEnd_Latitude());
+ trkvalues.put(KEY_TRACK_END_LONGITUDE, track.getEnd_Longitude());
+ trkvalues.put(KEY_TRACK_END_ALTITUDE, track.getEnd_Altitude());
+ trkvalues.put(KEY_TRACK_END_ACCURACY, track.getEnd_Accuracy());
+ trkvalues.put(KEY_TRACK_END_SPEED, track.getEnd_Speed());
+ trkvalues.put(KEY_TRACK_END_TIME, track.getEnd_Time());
+
+ trkvalues.put(KEY_TRACK_LASTSTEPDST_LATITUDE, track.getLastStepDistance_Latitude());
+ trkvalues.put(KEY_TRACK_LASTSTEPDST_LONGITUDE, track.getLastStepDistance_Longitude());
+ trkvalues.put(KEY_TRACK_LASTSTEPDST_ACCURACY, track.getLastStepDistance_Accuracy());
+
+ trkvalues.put(KEY_TRACK_LASTSTEPALT_ALTITUDE, track.getLastStepAltitude_Altitude());
+ trkvalues.put(KEY_TRACK_LASTSTEPALT_ACCURACY, track.getLastStepAltitude_Accuracy());
+
+ trkvalues.put(KEY_TRACK_MIN_LATITUDE, track.getMin_Latitude());
+ trkvalues.put(KEY_TRACK_MIN_LONGITUDE, track.getMin_Longitude());
+
+ trkvalues.put(KEY_TRACK_MAX_LATITUDE, track.getMax_Latitude());
+ trkvalues.put(KEY_TRACK_MAX_LONGITUDE, track.getMax_Longitude());
+
+ trkvalues.put(KEY_TRACK_DURATION, track.getDuration());
+ trkvalues.put(KEY_TRACK_DURATION_MOVING, track.getDuration_Moving());
+
+ trkvalues.put(KEY_TRACK_DISTANCE, track.getDistance());
+ trkvalues.put(KEY_TRACK_DISTANCE_INPROGRESS, track.getDistanceInProgress());
+ trkvalues.put(KEY_TRACK_DISTANCE_LASTALTITUDE, track.getDistanceLastAltitude());
+
+ trkvalues.put(KEY_TRACK_ALTITUDE_UP, track.getAltitude_Up());
+ trkvalues.put(KEY_TRACK_ALTITUDE_DOWN, track.getAltitude_Down());
+ trkvalues.put(KEY_TRACK_ALTITUDE_INPROGRESS, track.getAltitude_InProgress());
+
+ trkvalues.put(KEY_TRACK_SPEED_MAX, track.getSpeedMax());
+ trkvalues.put(KEY_TRACK_SPEED_AVERAGE, track.getSpeedAverage());
+ trkvalues.put(KEY_TRACK_SPEED_AVERAGEMOVING, track.getSpeedAverageMoving());
+
+ trkvalues.put(KEY_TRACK_NUMBEROFLOCATIONS, track.getNumberOfLocations());
+ trkvalues.put(KEY_TRACK_NUMBEROFPLACEMARKS, track.getNumberOfPlacemarks());
+ trkvalues.put(KEY_TRACK_TYPE, track.getType());
+
+ trkvalues.put(KEY_TRACK_VALIDMAP, track.getValidMap());
+
+ long TrackID;
+ // Inserting Row
+ TrackID = (db.insert(TABLE_TRACKS, null, trkvalues));
+
+ //Log.w("myApp", "[#] DatabaseHandler.java - addTrack " + TrackID);
+
+ return TrackID; // Insert this in the track ID !!!
+ }
+
+
+ // Get Track
+ public Track getTrack(long TrackID) {
+
+ Track track = null;
+
+ String selectQuery = "SELECT * FROM " + TABLE_TRACKS + " WHERE "
+ + KEY_ID + " = " + TrackID;
+
+ //Log.w("myApp", "[#] DatabaseHandler.java - getTrackList(" + startNumber + ", " +endNumber + ") ==> " + selectQuery);
+
+ SQLiteDatabase db = this.getWritableDatabase();
+ Cursor cursor = db.rawQuery(selectQuery, null);
+
+ //Log.w("myApp", "[#] DatabaseHandler.java - getTrack(" + TrackID + ")");
+
+ if (cursor != null) {
+ if (cursor.moveToFirst()) {
+ track = new Track();
+ track.FromDB(cursor.getLong(0),
+ cursor.getString(1),
+ cursor.getString(2),
+ cursor.getString(3),
+
+ cursor.getDouble(4),
+ cursor.getDouble(5),
+ cursor.getDouble(6),
+ cursor.getFloat(7),
+ cursor.getFloat(8),
+ cursor.getLong(9),
+
+ cursor.getLong(10),
+
+ cursor.getDouble(11),
+ cursor.getDouble(12),
+ cursor.getDouble(13),
+ cursor.getFloat(14),
+ cursor.getFloat(15),
+ cursor.getLong(16),
+
+ cursor.getDouble(17),
+ cursor.getDouble(18),
+ cursor.getFloat(19),
+
+ cursor.getDouble(20),
+ cursor.getFloat(21),
+
+ cursor.getDouble(22),
+ cursor.getDouble(23),
+ cursor.getDouble(24),
+ cursor.getDouble(25),
+
+ cursor.getLong(26),
+ cursor.getLong(27),
+
+ cursor.getFloat(28),
+ cursor.getFloat(29),
+ cursor.getLong(30),
+
+ cursor.getDouble(31),
+ cursor.getDouble(32),
+ cursor.getDouble(33),
+
+ cursor.getFloat(34),
+ cursor.getFloat(35),
+ cursor.getFloat(36),
+
+ cursor.getLong(37),
+ cursor.getLong(38),
+
+ cursor.getInt(39),
+ cursor.getInt(40));
+ }
+ cursor.close();
+ }
+ return track;
+ }
+
+
+ // Get last TrackID
+ public long getLastTrackID() {
+ SQLiteDatabase db = this.getWritableDatabase();
+ long result = 0;
+
+ String query = "SELECT " + KEY_ID + " FROM " + TABLE_TRACKS + " ORDER BY " + KEY_ID + " DESC LIMIT 1";
+
+ //Log.w("myApp", "[#] DatabaseHandler.java - getLastTrackID(): " + query);
+
+ Cursor cursor = db.rawQuery(query, null);
+
+ if (cursor != null) {
+ //Log.w("myApp", "[#] !null");
+ if (cursor.moveToFirst()) {
+ result = cursor.getLong(0);
+ } //else Log.w("myApp", "[#] Tracks table is empty");
+ cursor.close();
+ }
+ //Log.w("myApp", "[#] RETURN getLastTrack()");
+ //Log.w("myApp", "[#] DatabaseHandler.java - LastTrackID = " + result);
+ return result;
+ }
+
+
+ // Get last TrackID
+ public Track getLastTrack() {
+ return getTrack(getLastTrackID());
+ }
+
+
+ // Getting a list of Tracks, with number between startNumber and endNumber
+ // Please note that limits both are inclusive!
+ public List getTracksList(long startNumber, long endNumber) {
+
+ List trackList = new ArrayList<>();
+
+ String selectQuery = "SELECT * FROM " + TABLE_TRACKS + " WHERE "
+ + KEY_ID + " BETWEEN " + startNumber + " AND " + endNumber
+ + " ORDER BY " + KEY_ID + " DESC";
+
+ //Log.w("myApp", "[#] DatabaseHandler.java - getTrackList(" + startNumber + ", " +endNumber + ") ==> " + selectQuery);
+
+ SQLiteDatabase db = this.getWritableDatabase();
+ Cursor cursor = db.rawQuery(selectQuery, null);
+
+ if (cursor != null) {
+ // looping through all rows and adding to list
+ if (cursor.moveToFirst()) {
+ do {
+ Track trk = new Track();
+ trk.FromDB(cursor.getLong(0),
+ cursor.getString(1),
+ cursor.getString(2),
+ cursor.getString(3),
+
+ cursor.getDouble(4),
+ cursor.getDouble(5),
+ cursor.getDouble(6),
+ cursor.getFloat(7),
+ cursor.getFloat(8),
+ cursor.getLong(9),
+
+ cursor.getLong(10),
+
+ cursor.getDouble(11),
+ cursor.getDouble(12),
+ cursor.getDouble(13),
+ cursor.getFloat(14),
+ cursor.getFloat(15),
+ cursor.getLong(16),
+
+ cursor.getDouble(17),
+ cursor.getDouble(18),
+ cursor.getFloat(19),
+
+ cursor.getDouble(20),
+ cursor.getFloat(21),
+
+ cursor.getDouble(22),
+ cursor.getDouble(23),
+ cursor.getDouble(24),
+ cursor.getDouble(25),
+
+ cursor.getLong(26),
+ cursor.getLong(27),
+
+ cursor.getFloat(28),
+ cursor.getFloat(29),
+ cursor.getLong(30),
+
+ cursor.getDouble(31),
+ cursor.getDouble(32),
+ cursor.getDouble(33),
+
+ cursor.getFloat(34),
+ cursor.getFloat(35),
+ cursor.getFloat(36),
+
+ cursor.getLong(37),
+ cursor.getLong(38),
+
+ cursor.getInt(39),
+ cursor.getInt(40));
+
+ trackList.add(trk); // Add Track to list
+ } while (cursor.moveToNext());
+ }
+ cursor.close();
+ }
+ return trackList;
+ }
+
+
+ public void CorrectGPSWeekRollover() {
+ String CorrectLocationsQuery = "UPDATE " + TABLE_LOCATIONS + " SET " + KEY_LOCATION_TIME + " = " + KEY_LOCATION_TIME + " + 619315200000 WHERE "
+ + KEY_LOCATION_TIME + " <= 1388534400000 "; // 01/01/2014 00:00:00.000
+ String CorrectPlacemarksQuery = "UPDATE " + TABLE_PLACEMARKS + " SET " + KEY_LOCATION_TIME + " = " + KEY_LOCATION_TIME + " + 619315200000 WHERE "
+ + KEY_LOCATION_TIME + " <= 1388534400000 "; // 01/01/2014 00:00:00.000
+ String CorrectNamesQuery = "SELECT " + KEY_ID + "," + KEY_TRACK_NAME + " FROM " + TABLE_TRACKS + " WHERE "
+ + KEY_TRACK_NAME + " LIKE '199%'";
+
+ class IdAndName {
+ long id;
+ String Name;
+ }
+
+ ArrayList Names = new ArrayList<>();
+
+ //Log.w("myApp", "[#] DatabaseHandler.java - getTrackList(" + startNumber + ", " +endNumber + ") ==> " + selectQuery);
+
+ SQLiteDatabase db = this.getWritableDatabase();
+
+ // Correction of Locations
+ db.execSQL(CorrectLocationsQuery);
+
+ // Correction of Placemarks
+ db.execSQL(CorrectPlacemarksQuery);
+
+
+ // Correction of Track Names
+ Cursor cursor = db.rawQuery(CorrectNamesQuery, null);
+
+ if (cursor != null) {
+ int i = 0;
+ // looping through all rows and adding to list
+ if (cursor.moveToFirst()) {
+ do {
+ SimpleDateFormat SDF = new SimpleDateFormat("yyyyMMdd-HHmmss"); // date and time formatter
+ SDF.setTimeZone(TimeZone.getTimeZone("GMT"));
+ try {
+ Date d = SDF.parse(cursor.getString(1));
+ long timeInMilliseconds = d.getTime();
+ long timeInMilliseconds_Corrected = timeInMilliseconds + 619315200000L;
+ String Name_Corrected = SDF.format(timeInMilliseconds_Corrected);
+ //Log.w("myApp", "[#] GPSApplication.java - NAME CORRECTED FROM " + cursor.getString(0) + " TO " + Name_Corrected);
+ IdAndName IN = new IdAndName();
+ IN.id = cursor.getLong(0);
+ IN.Name = Name_Corrected;
+ Names.add(IN);
+ } catch (ParseException ex) {
+ Log.v("Exception", ex.getLocalizedMessage());
+ }
+ i++;
+ } while (cursor.moveToNext());
+ }
+ Log.w("myApp", "[#] DatabaseHandler.java - CorrectGPSWeekRollover NAMES = " + i);
+ cursor.close();
+ }
+
+ for (IdAndName N : Names) {
+ Log.w("myApp", "[#] GPSApplication.java - CORRECTING TRACK " + N.id + " = " + N.Name);
+ db.execSQL("UPDATE " + TABLE_TRACKS + " SET " + KEY_TRACK_NAME + " = \"" + N.Name + "\" WHERE " + KEY_ID + " = " + N.id);
+ }
+ }
+
+
+ // Getting the list of all Tracks in the DB
+ /* NOT USED, COMMENTED OUT !!
+ public List getTracksList() {
+
+ List trackList = new ArrayList();
+
+ String selectQuery = "SELECT * FROM " + TABLE_TRACKS
+ + " ORDER BY " + KEY_ID + " DESC";
+
+ //Log.w("myApp", "[#] DatabaseHandler.java - getTrackList() ==> " + selectQuery);
+
+ SQLiteDatabase db = this.getWritableDatabase();
+ Cursor cursor = db.rawQuery(selectQuery, null);
+
+ if (cursor != null) {
+ // looping through all rows and adding to list
+ if (cursor.moveToFirst()) {
+ do {
+ Track trk = new Track();
+ trk.FromDB(cursor.getLong(0),
+ cursor.getString(1),
+ cursor.getString(2),
+ cursor.getString(3),
+
+ cursor.getDouble(4),
+ cursor.getDouble(5),
+ cursor.getDouble(6),
+ cursor.getFloat(7),
+ cursor.getFloat(8),
+ cursor.getLong(9),
+
+ cursor.getLong(10),
+
+ cursor.getDouble(11),
+ cursor.getDouble(12),
+ cursor.getDouble(13),
+ cursor.getFloat(14),
+ cursor.getFloat(15),
+ cursor.getLong(16),
+
+ cursor.getDouble(17),
+ cursor.getDouble(18),
+ cursor.getFloat(19),
+
+ cursor.getDouble(20),
+ cursor.getFloat(21),
+
+ cursor.getDouble(22),
+ cursor.getDouble(23),
+ cursor.getDouble(24),
+ cursor.getDouble(25),
+
+ cursor.getLong(26),
+ cursor.getLong(27),
+
+ cursor.getFloat(28),
+ cursor.getFloat(29),
+ cursor.getLong(30),
+
+ cursor.getDouble(31),
+ cursor.getDouble(32),
+ cursor.getDouble(33),
+
+ cursor.getFloat(34),
+ cursor.getFloat(35),
+ cursor.getFloat(36),
+
+ cursor.getLong(37),
+ cursor.getLong(38),
+
+ cursor.getInt(39),
+ cursor.getInt(40));
+ trackList.add(trk); // Add Track to list
+ } while (cursor.moveToNext());
+ }
+ cursor.close();
+ }
+ return trackList;
+ } */
+}
\ No newline at end of file
diff --git a/app/src/main/java/Satellite/EGM96.java b/app/src/main/java/Satellite/EGM96.java
new file mode 100644
index 0000000..e1d9964
--- /dev/null
+++ b/app/src/main/java/Satellite/EGM96.java
@@ -0,0 +1,276 @@
+
+package Satellite;
+
+
+import android.util.Log;
+
+import org.greenrobot.eventbus.EventBus;
+
+import java.io.BufferedInputStream;
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+class EGM96 {
+
+ // ---------------------------------------------------------------------------- Singleton Class
+ private static EGM96 instance = new EGM96();
+
+ private EGM96(){}
+
+ public static EGM96 getInstance(){
+ return instance;
+ }
+
+
+
+ private int BOUNDARY = 3; // The grid extensions (in each of the 4 sides) of the real 721 x 1440 grid
+ private short[][] EGMGrid = new short[BOUNDARY + 1440 + BOUNDARY][BOUNDARY + 721 + BOUNDARY];
+ private boolean isEGMGridLoaded = false;
+ private boolean isEGMGridLoading = false;
+ private boolean isEGMFileCopying = false;
+ private String EGMFileName;
+ private String EGMFileNameLocalCopy;
+
+ public double EGM96_VALUE_INVALID = -100000;
+
+ public void LoadGridFromFile(String FileName, String FileNameLocalCopy) {
+ if (!isEGMGridLoaded && !isEGMGridLoading) {
+ isEGMGridLoading = true;
+ EGMFileName = FileName;
+ EGMFileNameLocalCopy = FileNameLocalCopy;
+ new Thread(new LoadEGM96Grid()).start();
+ } else {
+ if (isEGMGridLoading) Log.w("myApp", "[#] EGM96.java - Grid is already loading, please wait");
+ if (isEGMGridLoaded) Log.w("myApp", "[#] EGM96.java - Grid already loaded");
+ }
+ }
+
+ /* public void UnloadGrid() {
+ isEGMGridLoaded = false;
+ isEGMGridLoading = false;
+ //listener.onEGMGridLoaded(isEGMGridLoaded);
+ EventBus.getDefault().post(EventBusMSG.UPDATE_FIX);
+ EventBus.getDefault().post(EventBusMSG.UPDATE_TRACK);
+ EventBus.getDefault().post(EventBusMSG.UPDATE_TRACKLIST);
+ }
+*/
+ public boolean isEGMGridLoaded() {
+ return isEGMGridLoaded;
+ }
+
+ public boolean isEGMGridLoading() {
+ return isEGMGridLoading;
+ }
+
+ public double getEGMCorrection(double Latitude, double Longitude) {
+ // This function calculates and return the EGM96 altitude correction value of the input coordinates, in m;
+ // Input coordinates are: -90 < Latitude < 90; -180 < Longitude < 360 (android range -180 < Longitude < 180);
+
+ if (isEGMGridLoaded) {
+ double Lat = 90.0 - Latitude;
+ double Lon = Longitude;
+ if (Lon < 0) Lon += 360.0;
+
+ int ilon = (int) (Lon / 0.25) + BOUNDARY;
+ int ilat = (int) (Lat / 0.25) + BOUNDARY;
+
+ try {
+ // Creating points for interpolation
+ short hc11 = EGMGrid[ilon][ilat];
+ short hc12 = EGMGrid[ilon][ilat + 1];
+ short hc21 = EGMGrid[ilon + 1][ilat];
+ short hc22 = EGMGrid[ilon + 1][ilat + 1];
+
+ // Interpolation:
+ // Latitude
+ double hc1 = hc11 + (hc12 - hc11) * (Lat % 0.25) / 0.25;
+ double hc2 = hc21 + (hc22 - hc21) * (Lat % 0.25) / 0.25;
+ // Longitude
+ //double hc = (hc1 + (hc2 - hc1) * (Lon % 0.25) / 0.25) / 100;
+ //Log.w("myApp", "[#] EGM96.java - getEGMCorrection(" + Latitude + ", " + Longitude + ") = " + hc);
+
+ return ((hc1 + (hc2 - hc1) * (Lon % 0.25) / 0.25) / 100);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ return EGM96_VALUE_INVALID;
+ }
+ }
+ else return EGM96_VALUE_INVALID;
+ }
+
+ private void copyFile(InputStream in, OutputStream out) throws IOException {
+ byte[] buffer = new byte[1024];
+ int read;
+ while ((read = in.read(buffer)) != -1) {
+ out.write(buffer, 0, read);
+ }
+ }
+
+ private void DeleteFile(String filename) {
+ File file = new File(filename);
+ if (file.exists ()) file.delete();
+ }
+
+
+ // The Thread that loads the grid in background ------------------------------------------------
+
+ private class LoadEGM96Grid implements Runnable {
+ // Thread: Load EGM grid
+
+ @Override
+ public void run() {
+ Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
+ Log.w("myApp", "[#] EGM96.java - Start loading grid");
+
+ boolean islocalcopypresent = false;
+ boolean issharedcopypresent = false;
+
+ File localfile = new File(EGMFileNameLocalCopy);
+ if (localfile.exists() && (localfile.length() == 2076480)) islocalcopypresent = true;
+
+ File sharedfile = new File(EGMFileName);
+ if (sharedfile.exists() && (sharedfile.length() == 2076480)) issharedcopypresent = true;
+
+ File file = new File(islocalcopypresent ? EGMFileNameLocalCopy : EGMFileName);
+ if (islocalcopypresent || issharedcopypresent) {
+ Log.w("myApp", "[#] EGM96.java - From file: " + file.getAbsolutePath());
+
+ FileInputStream fin;
+ try {
+ fin = new FileInputStream(file);
+ } catch (FileNotFoundException e) {
+ isEGMGridLoaded = false;
+ isEGMGridLoading = false;
+ Log.w("myApp", "[#] EGM96.java - FileNotFoundException");
+ //Toast.makeText(getApplicationContext(), "Oops", Toast.LENGTH_SHORT).show();
+ //e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
+ return;
+ }
+ BufferedInputStream bin = new BufferedInputStream(fin);
+ DataInputStream din = new DataInputStream(bin);
+ int i;
+ int i_lon = BOUNDARY;
+ int i_lat = BOUNDARY;
+ int count = (int) ((file.length() / 2));
+
+ for (i = 0; (i < count); i++) {
+ try {
+ EGMGrid[i_lon][i_lat] = din.readShort();
+ i_lon++;
+ if (i_lon >= (1440 + BOUNDARY)) {
+ i_lat++;
+ i_lon = BOUNDARY;
+ }
+ } catch (IOException e) {
+ isEGMGridLoaded = false;
+ isEGMGridLoading = false;
+ Log.w("myApp", "[#] EGM96.java - IOException");
+ return;
+ //Toast.makeText(getApplicationContext(), "Oops", Toast.LENGTH_SHORT).show();
+ //e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
+ }
+ }
+
+ if (BOUNDARY > 0) {
+ // Fill boundaries with correct data, in order to speed up retrieving for interpolation;
+ // fill left + right boundaries
+ //Log.w("myApp", "[#] EGM96.java - LR BOUNDARIES");
+ for (int ix = 0; (ix < BOUNDARY); ix++) {
+ for (int iy = BOUNDARY; (iy < BOUNDARY + 721); iy++) {
+ EGMGrid[ix][iy] = EGMGrid[ix + 1440][iy];
+ EGMGrid[BOUNDARY + ix + 1440][iy] = EGMGrid[BOUNDARY + ix][iy];
+ }
+ }
+ // fill top + bottom boundaries
+ //Log.w("myApp", "[#] EGM96.java - TOP DOWN BOUNDARIES");
+ for (int iy = 0; (iy < BOUNDARY); iy++) {
+ for (int ix = 0; (ix < BOUNDARY + 1440 + BOUNDARY); ix++) {
+ if (ix > 720) {
+ EGMGrid[ix][iy] = EGMGrid[ix - 720][BOUNDARY + BOUNDARY - iy];
+ EGMGrid[ix][BOUNDARY + iy + 721] = EGMGrid[ix - 720][BOUNDARY + 721-2 - iy];
+ }
+ else {
+ EGMGrid[ix][iy] = EGMGrid[ix + 720][BOUNDARY + BOUNDARY - iy];
+ EGMGrid[ix][BOUNDARY + iy + 721] = EGMGrid[ix + 720][BOUNDARY + 721-2 - iy];
+ }
+ }
+ }
+ }
+
+ isEGMGridLoading = false;
+ isEGMGridLoaded = true;
+ Log.w("myApp", "[#] EGM96.java - Grid Successfully Loaded: " + file.getAbsolutePath());
+ //Toast.makeText(getApplicationContext(), "EGM96 correction grid loaded", Toast.LENGTH_SHORT).show();
+
+ if (issharedcopypresent) {
+ if (!islocalcopypresent) new Thread(new CopyEGM96Grid()).start();
+ else {
+ DeleteFile(EGMFileName); // Delete the EGM file from the shared folder
+ Log.w("myApp", "[#] EGM96.java - EGM File already present into FilesDir. File deleted from shared folder");
+ }
+ }
+
+ } else {
+ isEGMGridLoading = false;
+ isEGMGridLoaded = false;
+ if (!file.exists()) { Log.w("myApp", "[#] EGM96.java - File not found"); }
+ if (!file.canRead()) { Log.w("myApp", "[#] EGM96.java - Cannot read file"); }
+ if (file.length() != 2076480) {
+ Log.w("myApp", "[#] EGM96.java - File has invalid length: " + file.length());}
+ //Toast.makeText(getApplicationContext(), "EGM96 correction not available", Toast.LENGTH_SHORT).show();
+ }
+ EventBus.getDefault().post(EventBusMSG.UPDATE_FIX);
+ EventBus.getDefault().post(EventBusMSG.UPDATE_TRACK);
+ EventBus.getDefault().post(EventBusMSG.UPDATE_TRACKLIST);
+ //listener.onEGMGridLoaded(isEGMGridLoaded);
+ }
+ }
+
+ // The Thread that copies the EGM grid in FilesDir (in background) -----------------------------
+
+ private class CopyEGM96Grid implements Runnable {
+ // Thread: Copy the EGM grid in FilesDir
+
+ @Override
+ public void run() {
+ Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
+ Log.w("myApp", "[#] EGM96.java - Copy EGM96 Grid into FilesDir");
+
+ if (isEGMFileCopying) return;
+
+ isEGMFileCopying = true;
+
+ File sd_cpy = new File(EGMFileNameLocalCopy);
+ if (sd_cpy.exists()) sd_cpy.delete();
+
+ File sd_old = new File(EGMFileName);
+ if (sd_old.exists()) {
+ InputStream in = null;
+ OutputStream out = null;
+ try {
+ in = new FileInputStream(EGMFileName);
+ out = new FileOutputStream(EGMFileNameLocalCopy);
+ copyFile(in, out);
+ in.close();
+ in = null;
+ out.flush();
+ out.close();
+ out = null;
+ Log.w("myApp", "[#] EGM96.java - EGM File copy completed");
+ DeleteFile(EGMFileName); // Delete the EGM file from the shared folder
+ Log.w("myApp", "[#] EGM96.java - EGM File deleted from shared folder");
+
+ } catch(Exception e) {
+ Log.w("MyApp", "[#] EGM96.java - Unable to make local copy of EGM file: " + e.getMessage());
+ }
+ }
+
+ isEGMFileCopying = false;
+ }
+ }
+}
diff --git a/app/src/main/java/Satellite/EventBusMSG.java b/app/src/main/java/Satellite/EventBusMSG.java
new file mode 100644
index 0000000..7eeacfd
--- /dev/null
+++ b/app/src/main/java/Satellite/EventBusMSG.java
@@ -0,0 +1,32 @@
+
+package Satellite;
+
+public class EventBusMSG {
+
+ static final short APP_RESUME = 1; // Sent to components on app resume
+ static final short APP_PAUSE = 2; // Sent to components on app pause
+ static final short NEW_TRACK = 3; // Request to create a new track
+ static final short UPDATE_FIX = 4; // Notify that a new fix is available
+ static final short UPDATE_TRACK = 5; // Notify that the current track stats are updated
+ static final short UPDATE_TRACKLIST = 6; // Notify that the tracklist is changed
+ static final short UPDATE_SETTINGS = 7; // Tell that settings are changed
+ static final short REQUEST_ADD_PLACEMARK = 8; // The user ask to add a placemark
+ static final short ADD_PLACEMARK = 9; // The placemark is available
+ static final short APPLY_SETTINGS = 10; // The new settings must be applied
+ static final short TOAST_TRACK_EXPORTED = 11; // The exporter has finished to export the track, shows toast
+ static final short TOAST_STORAGE_PERMISSION_REQUIRED= 12; // The Storage permission is required
+ static final short UPDATE_JOB_PROGRESS = 13; // Update the progress of the current Job
+ static final short NOTIFY_TRACKS_DELETED = 14; // Notify that some tracks are deleted
+ static final short UPDATE_ACTIONBAR = 15; // Notify that the actionbar must be updated
+ static final short REFRESH_TRACKLIST = 16; // Refresh the tracklist, without update it from DB
+
+ static final short TRACKLIST_DESELECT = 24; // The user deselect (into the tracklist) the track with a given id
+ static final short TRACKLIST_SELECT = 25; // The user select (into the tracklist) the track with a given id
+ static final short INTENT_SEND = 26; // Request to share
+ static final short TOAST_UNABLE_TO_WRITE_THE_FILE = 27; // Exporter fails to export the Track (given id)
+
+ static final short ACTION_BULK_DELETE_TRACKS = 40; // Delete the selected tracks
+ static final short ACTION_BULK_EXPORT_TRACKS = 41; // Export the selected tracks
+ static final short ACTION_BULK_VIEW_TRACKS = 42; // View the selected tracks
+ static final short ACTION_BULK_SHARE_TRACKS = 43; // Share the selected tracks
+}
diff --git a/app/src/main/java/Satellite/ExportingTask.java b/app/src/main/java/Satellite/ExportingTask.java
new file mode 100644
index 0000000..ace345f
--- /dev/null
+++ b/app/src/main/java/Satellite/ExportingTask.java
@@ -0,0 +1,58 @@
+
+package Satellite;
+
+public class ExportingTask {
+
+ static final short STATUS_PENDING = 0; // Task not yet started
+ static final short STATUS_RUNNING = 1; // Task is running...
+ static final short STATUS_ENDED_SUCCESS = 2; // Task ended with success
+ static final short STATUS_ENDED_FAILED = 3; // Task failed to export
+
+ private long id = 0;
+ private long NumberOfPoints_Total = 0;
+ private long NumberOfPoints_Processed = 0;
+ private short Status = STATUS_PENDING;
+ private String Name = "";
+
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public long getNumberOfPoints_Total() {
+ return NumberOfPoints_Total;
+ }
+
+ public void setNumberOfPoints_Total(long numberOfPoints_Total) {
+ NumberOfPoints_Total = numberOfPoints_Total;
+ }
+
+ public long getNumberOfPoints_Processed() {
+ return NumberOfPoints_Processed;
+ }
+
+ public void setNumberOfPoints_Processed(long numberOfPoints_Processed) {
+ NumberOfPoints_Processed = numberOfPoints_Processed;
+ }
+
+ public short getStatus() {
+ return Status;
+ }
+
+ public void setStatus(short status) {
+ Status = status;
+ }
+
+ public String getName() {
+ return Name;
+ }
+
+ public void setName(String name) {
+ Name = name;
+ }
+}
+
diff --git a/app/src/main/java/Satellite/FragmentGPSFix.java b/app/src/main/java/Satellite/FragmentGPSFix.java
new file mode 100644
index 0000000..82f3445
--- /dev/null
+++ b/app/src/main/java/Satellite/FragmentGPSFix.java
@@ -0,0 +1,290 @@
+
+package Satellite;
+
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.text.method.ScrollingMovementMethod;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.TableLayout;
+import android.widget.TextView;
+
+import org.greenrobot.eventbus.EventBus;
+import org.greenrobot.eventbus.Subscribe;
+import org.greenrobot.eventbus.ThreadMode;
+
+import fr.geolabs.dev.mapmint4me.R;
+
+public class FragmentGPSFix extends Fragment {
+
+ private static final int NOT_AVAILABLE = -100000;
+
+ private final int GPS_DISABLED = 0;
+ private final int GPS_OUTOFSERVICE = 1;
+ private final int GPS_TEMPORARYUNAVAILABLE = 2;
+ private final int GPS_SEARCHING = 3;
+ private final int GPS_STABILIZING = 4;
+ private final int GPS_OK = 5;
+
+ private PhysicalDataFormatter phdformatter = new PhysicalDataFormatter();
+
+ private FrameLayout FLGPSFix;
+
+ private TextView TVLatitude;
+ private TextView TVLongitude;
+ private TextView TVLatitudeUM;
+ private TextView TVLongitudeUM;
+ private TextView TVAltitude;
+ private TextView TVAltitudeUM;
+ private TextView TVSpeed;
+ private TextView TVSpeedUM;
+ private TextView TVBearing;
+ private TextView TVAccuracy;
+ private TextView TVAccuracyUM;
+ private TextView TVGPSFixStatus;
+ private TextView TVDirectionUM;
+ private TextView TVTime;
+ public TextView TVSatellites;
+ public TextView TVSatellites1;
+ private TableLayout TLCoordinates;
+ private TableLayout TLAltitude;
+ private TableLayout TLSpeed;
+ private TableLayout TLBearing;
+ private TableLayout TLAccuracy;
+ private TableLayout TLTime;
+ public TableLayout TLSatellites;
+
+ private LinearLayout LLTimeSatellites;
+
+ private PhysicalData phdLatitude;
+ private PhysicalData phdLongitude;
+ private PhysicalData phdAltitude;
+ private PhysicalData phdSpeed;
+ private PhysicalData phdBearing;
+ private PhysicalData phdAccuracy;
+ private PhysicalData phdTime;
+
+ final GPSApplication gpsApplication = GPSApplication.getInstance();
+
+ public FragmentGPSFix() {
+ // Required empty public constructor
+ }
+
+ @Subscribe (threadMode = ThreadMode.MAIN)
+ public void onEvent(Short msg) {
+ if (msg == EventBusMSG.UPDATE_FIX) {
+ Update();
+ }
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ // Inflate the layout for this fragment
+ View view = inflater.inflate(R.layout.fragment_gpsfix, container, false);
+
+ // FrameLayouts
+ FLGPSFix = view.findViewById(R.id.id_fragmentgpsfixFrameLayout);
+
+ // TextViews
+ TVLatitude = view.findViewById(R.id.id_textView_Latitude);
+ TVLongitude = view.findViewById(R.id.id_textView_Longitude);
+ TVLatitudeUM = view.findViewById(R.id.id_textView_LatitudeUM);
+ TVLongitudeUM = view.findViewById(R.id.id_textView_LongitudeUM);
+ TVAltitude = view.findViewById(R.id.id_textView_Altitude);
+ TVAltitudeUM = view.findViewById(R.id.id_textView_AltitudeUM);
+ TVSpeed = view.findViewById(R.id.id_textView_Speed);
+ TVSpeedUM = view.findViewById(R.id.id_textView_SpeedUM);
+ TVBearing = view.findViewById(R.id.id_textView_Bearing);
+ TVAccuracy = view.findViewById(R.id.id_textView_Accuracy);
+ TVAccuracyUM = view.findViewById(R.id.id_textView_AccuracyUM);
+ TVGPSFixStatus = view.findViewById(R.id.id_textView_GPSFixStatus);
+ TVDirectionUM = view.findViewById(R.id.id_textView_BearingUM);
+ TVTime = view.findViewById(R.id.id_textView_Time);
+ TVSatellites = view.findViewById(R.id.id_textView_satellite1);
+ TVSatellites1 = view.findViewById(R.id.id_textView_satellite2);
+ TVSatellites1.setMovementMethod(new ScrollingMovementMethod());
+
+
+ // TableLayouts
+ TLCoordinates = view.findViewById(R.id.id_TableLayout_Coordinates) ;
+ TLAltitude = view.findViewById(R.id.id_TableLayout_Altitude);
+ TLSpeed = view.findViewById(R.id.id_TableLayout_Speed);
+ TLBearing = view.findViewById(R.id.id_TableLayout_Bearing);
+ TLAccuracy = view.findViewById(R.id.id_TableLayout_Accuracy);
+ TLTime = view.findViewById(R.id.id_TableLayout_Time);
+ TLSatellites = view.findViewById(R.id.id_TableLayout_Satellites);
+
+ // LinearLayouts
+ LLTimeSatellites = view.findViewById(R.id.id_linearLayout_Time_Satellites);
+
+ TVGPSFixStatus.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (GPSStatus == GPS_DISABLED) {
+ if (GPSApplication.getInstance().getLocationSettingsFlag()) {
+ // This is the second click
+ GPSApplication.getInstance().setLocationSettingsFlag(false);
+ // Go to Settings screen
+ Intent callGPSSettingIntent = new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS);
+ if (callGPSSettingIntent != null) startActivityForResult(callGPSSettingIntent, 0);
+ } else {
+ GPSApplication.getInstance().setLocationSettingsFlag(true); // Start the timer
+ }
+ }
+ }
+ });
+
+ return view;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ // Workaround for Nokia Devices, Android 9
+ // https://github.com/BasicAirData/GPSLogger/issues/77
+ if (EventBus.getDefault().isRegistered(this)) {
+ //Log.w("myApp", "[#] FragmentGPSFix.java - EventBus: FragmentGPSFix already registered");
+ EventBus.getDefault().unregister(this);
+ }
+
+ EventBus.getDefault().register(this);
+ Update();
+ }
+
+ @Override
+ public void onPause() {
+ EventBus.getDefault().unregister(this);
+ super.onPause();
+ }
+
+
+ private LocationExtended location;
+ private double AltitudeManualCorrection;
+ private int prefDirections;
+ private int GPSStatus = GPS_DISABLED;
+ private boolean EGMAltitudeCorrection;
+ private boolean isValidAltitude;
+
+ public void Update() {
+ //Log.w("myApp", "[#] FragmentGPSFix.java - Update(Location location)");
+ location = gpsApplication.getCurrentLocationExtended();
+ AltitudeManualCorrection = gpsApplication.getPrefAltitudeCorrection();
+ prefDirections = gpsApplication.getPrefShowDirections();
+ GPSStatus = gpsApplication.getGPSStatus();
+ EGMAltitudeCorrection = gpsApplication.getPrefEGM96AltitudeCorrection();
+ if (isAdded()) {
+ if ((location != null) && (GPSStatus == GPS_OK)) {
+
+ phdLatitude = phdformatter.format(location.getLatitude(), PhysicalDataFormatter.FORMAT_LATITUDE);
+ phdLongitude = phdformatter.format(location.getLongitude(), PhysicalDataFormatter.FORMAT_LONGITUDE);
+ phdAltitude = phdformatter.format(location.getAltitudeCorrected(AltitudeManualCorrection, EGMAltitudeCorrection), PhysicalDataFormatter.FORMAT_ALTITUDE);
+ phdSpeed = phdformatter.format(location.getSpeed(), PhysicalDataFormatter.FORMAT_SPEED);
+ phdBearing = phdformatter.format(location.getBearing(), PhysicalDataFormatter.FORMAT_BEARING);
+ phdAccuracy = phdformatter.format(location.getAccuracy(), PhysicalDataFormatter.FORMAT_ACCURACY);
+ phdTime = phdformatter.format(location.getTime(), PhysicalDataFormatter.FORMAT_TIME);
+
+ TVLatitude.setText(phdLatitude.Value);
+ TVLongitude.setText(phdLongitude.Value);
+ TVLatitudeUM.setText(phdLatitude.UM);
+ TVLongitudeUM.setText(phdLongitude.UM);
+ //TVAltitude.setText(phdAltitude.Value);TVAltitudeUM.setText(phdAltitude.UM);
+ //TVSpeed.setText(phdSpeed.Value);
+ //TVSpeedUM.setText(phdSpeed.UM);
+ //TVBearing.setText(phdBearing.Value);
+ //TVAccuracy.setText(phdAccuracy.Value);
+ //TVAccuracyUM.setText(phdAccuracy.UM);
+ // TVTime.setText(phdTime.Value);
+ TVSatellites.setText(location.getNumberOfSatellitesUsedInFix() != NOT_AVAILABLE ? location.getNumberOfSatellitesUsedInFix() + "/" + location.getNumberOfSatellites() : "");
+ int i =0;
+ while(i<10) {
+
+ TVSatellites1.setText(location.getSatellite_info());
+ i = i + 1;
+ }
+ // Colorize the Altitude textview depending on the altitude EGM Correction
+ isValidAltitude = EGMAltitudeCorrection && (location.getAltitudeEGM96Correction() != NOT_AVAILABLE);
+ TVAltitude.setTextColor(isValidAltitude ? getResources().getColor(R.color.textColorPrimary) : getResources().getColor(R.color.textColorSecondary));
+ TVAltitudeUM.setTextColor(isValidAltitude ? getResources().getColor(R.color.textColorPrimary) : getResources().getColor(R.color.textColorSecondary));
+
+ TVGPSFixStatus.setVisibility(View.GONE);
+
+ TVDirectionUM.setVisibility(prefDirections == 0 ? View.GONE : View.VISIBLE);
+
+ TLCoordinates.setVisibility(phdLatitude.Value.equals("") ? View.INVISIBLE : View.VISIBLE);
+ TLAltitude.setVisibility(phdAltitude.Value.equals("") ? View.INVISIBLE : View.VISIBLE);
+ TLSpeed.setVisibility(phdSpeed.Value.equals("") ? View.INVISIBLE : View.VISIBLE);
+ TLBearing.setVisibility(phdBearing.Value.equals("") ? View.INVISIBLE : View.VISIBLE);
+ TLAccuracy.setVisibility(phdAccuracy.Value.equals("") ? View.INVISIBLE : View.VISIBLE);
+ TLTime.setVisibility(View.VISIBLE);
+ TLSatellites.setVisibility(location.getNumberOfSatellitesUsedInFix() == NOT_AVAILABLE ? View.INVISIBLE : View.VISIBLE);
+
+ FLGPSFix.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ FLGPSFix.removeOnLayoutChangeListener(this);
+
+ int ViewHeight = TLTime.getMeasuredHeight() + (int)(6*getResources().getDisplayMetrics().density);
+ int LayoutHeight = FLGPSFix.getHeight() - (int)(6*getResources().getDisplayMetrics().density);
+ //Log.w("myApp", "[#]");
+ //Log.w("myApp", "[#] -----------------------------------");
+ boolean isTimeAndSatellitesVisible;
+ if(getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT){
+ isTimeAndSatellitesVisible = LayoutHeight >= 6*ViewHeight;
+ //Log.w("myApp", "[#] " + LayoutHeight + " / " + 6*ViewHeight + " -> " + isTimeAndSatellitesVisible);
+ } else {
+ isTimeAndSatellitesVisible = LayoutHeight >= 4*ViewHeight;
+ //Log.w("myApp", "[#] " + LayoutHeight + " / " + 4*ViewHeight + " -> " + isTimeAndSatellitesVisible);
+ }
+ LLTimeSatellites.setVisibility(isTimeAndSatellitesVisible ? View.VISIBLE : View.GONE);
+
+ //Log.w("myApp", "[#] -----------------------------------");
+ //Log.w("myApp", "[#] Available Height = " + LayoutHeight + " px");
+ //Log.w("myApp", "[#] Density = " + getResources().getDisplayMetrics().density);
+ //Log.w("myApp", "[#] Tile Height = " + ViewHeight + " px");
+ }
+ });
+
+ } else {
+ TLCoordinates.setVisibility(View.INVISIBLE);
+ TLAltitude.setVisibility(View.INVISIBLE);
+ TLSpeed.setVisibility(View.INVISIBLE);
+ TLBearing.setVisibility(View.INVISIBLE);
+ TLAccuracy.setVisibility(View.INVISIBLE);
+ TLTime.setVisibility(View.INVISIBLE);
+ TLSatellites.setVisibility(View.INVISIBLE);
+
+ TVGPSFixStatus.setVisibility(View.VISIBLE);
+ switch (GPSStatus) {
+ case GPS_DISABLED:
+ TVGPSFixStatus.setText(R.string.gps_disabled_with_hint);
+ break;
+ case GPS_OUTOFSERVICE:
+ TVGPSFixStatus.setText(R.string.gps_out_of_service);
+ break;
+ case GPS_TEMPORARYUNAVAILABLE:
+ //TVGPSFixStatus.setText(R.string.gps_temporary_unavailable);
+ //break;
+ case GPS_SEARCHING:
+ TVGPSFixStatus.setText(R.string.gps_searching);
+ break;
+ case GPS_STABILIZING:
+ TVGPSFixStatus.setText(R.string.gps_stabilizing);
+ break;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/Satellite/GPSActivity.java b/app/src/main/java/Satellite/GPSActivity.java
new file mode 100644
index 0000000..f778f23
--- /dev/null
+++ b/app/src/main/java/Satellite/GPSActivity.java
@@ -0,0 +1,583 @@
+
+package Satellite;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.provider.Settings;
+import android.support.annotation.NonNull;
+import android.support.design.widget.BottomSheetBehavior;
+import android.support.design.widget.TabLayout;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentPagerAdapter;
+import android.support.v4.content.ContextCompat;
+import android.support.v4.view.ViewPager;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.preference.PreferenceManager;
+import android.support.v7.view.ActionMode;
+import android.support.v7.view.ContextThemeWrapper;
+import android.support.v7.widget.Toolbar;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Toast;
+
+import org.greenrobot.eventbus.EventBus;
+import org.greenrobot.eventbus.Subscribe;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import fr.geolabs.dev.mapmint4me.R;
+
+public class GPSActivity extends AppCompatActivity {
+
+ private static final int REQUEST_ID_MULTIPLE_PERMISSIONS = 1;
+
+ private final GPSApplication GPSApp = GPSApplication.getInstance();
+
+ private Toolbar toolbar;
+ private TabLayout tabLayout;
+ private ViewPager viewPager;
+ private ActionMode actionMode;
+ private View bottomSheet;
+ private MenuItem menutrackfinished = null;
+ private int activeTab = 1;
+ final Context context = this;
+
+ private boolean prefKeepScreenOn = true;
+ private boolean show_toast_grant_storage_permission = false;
+ private int theme;
+
+ private BottomSheetBehavior mBottomSheetBehavior;
+
+ Toast ToastClickAgain;
+
+
+ @Override
+ public void onRestart(){
+ Log.w("myApp", "[#] " + this + " - onRestart()");
+
+ if (Integer.valueOf(PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).getString("prefColorTheme", "2")) != theme) {
+ Log.w("myApp", "[#] GPSActivity.java - it needs to be recreated (Theme changed)");
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ // Normal behaviour for Android 5 +
+ this.recreate();
+ } else {
+ // Workaround to a bug on Android 4.4.X platform (google won't fix because Android 4.4 is obsolete)
+ // Android 4.4.X: taskAffinity & launchmode='singleTask' violating Activity's lifecycle
+ // https://issuetracker.google.com/issues/36998700
+ finish();
+ startActivity(new Intent(this, getClass()));
+ }
+ overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
+ }
+ super.onRestart();
+ }
+
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ Log.w("myApp", "[#] " + this + " - onCreate()");
+
+ setTheme(R.style.MyMaterialTheme);
+ theme = Integer.valueOf(PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).getString("prefColorTheme", "2"));
+
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.activity_gps);
+ toolbar = findViewById(R.id.id_toolbar);
+ setSupportActionBar(toolbar);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(false);
+ viewPager = findViewById(R.id.id_viewpager);
+ viewPager.setOffscreenPageLimit(3);
+
+ setupViewPager(viewPager);
+
+ tabLayout = findViewById(R.id.id_tablayout);
+ tabLayout.setTabMode(TabLayout.MODE_FIXED);
+ tabLayout.setupWithViewPager(viewPager);
+
+ tabLayout.addOnTabSelectedListener(
+ new TabLayout.ViewPagerOnTabSelectedListener(viewPager) {
+ @Override
+ public void onTabSelected(TabLayout.Tab tab) {
+ super.onTabSelected(tab);
+ activeTab = tab.getPosition();
+ GPSApp.setGPSActivity_activeTab(activeTab);
+ updateBottomSheetPosition();
+ ActivateActionModeIfNeeded();
+ }
+ });
+
+ bottomSheet = findViewById( R.id.id_bottomsheet );
+
+ mBottomSheetBehavior = BottomSheetBehavior.from(bottomSheet);
+ mBottomSheetBehavior.setHideable (false);
+
+ ToastClickAgain = Toast.makeText(this, getString(R.string.toast_track_finished_click_again), Toast.LENGTH_SHORT);
+ }
+
+
+ @Override
+ public void onStart() {
+ Log.w("myApp", "[#] " + this + " - onStart()");
+ super.onStart();
+ activeTab = tabLayout.getSelectedTabPosition();
+ System.out.println(activeTab);
+ GPSApp.setGPSActivity_activeTab(activeTab);
+ }
+
+
+ @Override
+ public void onStop() {
+ Log.w("myApp", "[#] " + this + " - onStop()");
+ super.onStop();
+ }
+
+
+ @Override
+ public void onResume() {
+ Log.w("myApp", "[#] " + this + " - onResume()");
+ super.onResume();
+
+ // Workaround for Nokia Devices, Android 9
+ // https://github.com/BasicAirData/GPSLogger/issues/77
+ if (EventBus.getDefault().isRegistered(this)) {
+ //Log.w("myApp", "[#] GPSActivity.java - EventBus: GPSActivity already registered");
+ EventBus.getDefault().unregister(this);
+ }
+
+ EventBus.getDefault().register(this);
+ LoadPreferences();
+ EventBus.getDefault().post(EventBusMSG.APP_RESUME);
+ if (menutrackfinished != null) menutrackfinished.setVisible(!GPSApp.getCurrentTrack().getName().equals(""));
+
+ // Check for Location runtime Permissions (for Android 23+)
+ if (!GPSApp.isLocationPermissionChecked()) {
+ CheckLocationPermission();
+ GPSApp.setLocationPermissionChecked(true);
+ }
+
+ ActivateActionModeIfNeeded();
+
+ if (GPSApp.FlagExists(GPSApp.FLAG_RECORDING) && !GPSApp.getRecording()) {
+ // The app is crashed in background
+ Log.w("myApp", "[#] GPSActivity.java - THE APP HAS BEEN KILLED IN BACKGROUND DURING A RECORDING !!!");
+ GPSApp.FlagRemove(GPSApp.FLAG_RECORDING);
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(new ContextThemeWrapper(this, R.style.StyledDialog));
+ if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ builder.setMessage(getResources().getString(R.string.dlg_app_killed) + "\n\n" + getResources().getString(R.string.dlg_app_killed_description));
+ builder.setNeutralButton(R.string.open_android_app_settings, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ Intent intent = new Intent();
+ intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+ Uri uri = Uri.fromParts("package", getPackageName(), null);
+ intent.setData(uri);
+ try {
+ startActivity(intent);
+ } catch (Exception e) {
+ //Log.w("myApp", "[#] GPSActivity.java - Unable to open the settings screen");
+ }
+ dialog.dismiss();
+ }
+ });
+ }
+ else builder.setMessage(getResources().getString(R.string.dlg_app_killed));
+ builder.setIcon(android.R.drawable.ic_menu_info_details);
+ builder.setPositiveButton(R.string.about_ok, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.dismiss();
+ }
+ });
+
+ AlertDialog dialog = builder.create();
+ dialog.show();
+ }
+
+ if (GPSApp.isJustStarted() && (GPSApp.getCurrentTrack().getNumberOfLocations() + GPSApp.getCurrentTrack().getNumberOfPlacemarks() > 0)) {
+ Toast.makeText(this.context, getString(R.string.toast_active_track_not_empty), Toast.LENGTH_LONG).show();
+ GPSApp.setJustStarted(false);
+ } else GPSApp.setJustStarted(false);
+
+ if (show_toast_grant_storage_permission) {
+ Toast.makeText(this.context, getString(R.string.please_grant_storage_permission), Toast.LENGTH_LONG).show();
+ show_toast_grant_storage_permission = false;
+ }
+ }
+
+
+ @Override
+ public void onPause() {
+ Log.w("myApp", "[#] " + this + " - onPause()");
+ EventBus.getDefault().post(EventBusMSG.APP_PAUSE);
+ EventBus.getDefault().unregister(this);
+ super.onPause();
+ }
+
+
+ @Override
+ public void onBackPressed() {
+ ShutdownApp();
+ }
+
+
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ super.onWindowFocusChanged(hasFocus);
+ updateBottomSheetPosition();
+ }
+
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.main_menu, menu);
+ return true;
+ }
+
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ menutrackfinished = menu.findItem(R.id.action_track_finished);
+ menutrackfinished.setVisible(!GPSApp.getCurrentTrack().getName().equals(""));
+ return true;
+ }
+
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ int id = item.getItemId();
+
+ /* if (id == R.id.action_settings) {
+ GPSApp.setHandlerTimer(60000);
+ // Intent intent = new Intent(this, SettingsActivity.class);
+ // startActivity(intent);
+ return true;
+ } */
+ if (id == R.id.action_track_finished) {
+ if (GPSApp.getNewTrackFlag()) {
+ // This is the second click
+ GPSApp.setNewTrackFlag(false);
+ GPSApp.setRecording(false);
+ EventBus.getDefault().post(EventBusMSG.NEW_TRACK);
+ ToastClickAgain.cancel();
+ Toast.makeText(this, getString(R.string.toast_track_saved_into_tracklist), Toast.LENGTH_SHORT).show();
+ } else {
+ // This is the first click
+ GPSApp.setNewTrackFlag(true); // Start the timer
+ ToastClickAgain.show();
+ }
+ return true;
+ }
+ /* if (id == R.id.action_about) {
+ // Show About Dialog
+ FragmentManager fm = getSupportFragmentManager();
+ FragmentAboutDialog aboutDialog = new FragmentAboutDialog();
+ aboutDialog.show(fm, "");
+ return true;
+ } */
+ /* if (id == R.id.action_shutdown) {
+ ShutdownApp();
+ return true;
+ }*/
+ return super.onOptionsItemSelected(item);
+ }
+
+
+ private void updateBottomSheetPosition() {
+ activeTab = tabLayout.getSelectedTabPosition();
+ if (activeTab != 2) {
+ mBottomSheetBehavior.setPeekHeight(1);
+ mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
+ //Log.w("myApp", "[#] GPSActivity.java - mBottomSheetBehavior.setPeekHeight(" + bottomSheet.getHeight() +");");
+ mBottomSheetBehavior.setPeekHeight(bottomSheet.getHeight());
+ } else {
+ mBottomSheetBehavior.setPeekHeight(1);
+ mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED) ;
+ }
+ }
+
+
+ private void setupViewPager(ViewPager viewPager) {
+ ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager());
+ adapter.addFragment(new FragmentGPSFix(), getString(R.string.tab_gpsfix1));
+ //adapter.addFragment(new FragmentTrack(), getString(R.string.tab_track));
+ //adapter.addFragment(new FragmentTracklist(), getString(R.string.tab_tracklist));
+ viewPager.setAdapter(adapter);
+ }
+
+ class ViewPagerAdapter extends FragmentPagerAdapter {
+ private final List mFragmentList = new ArrayList<>();
+ private final List mFragmentTitleList = new ArrayList<>();
+
+ public ViewPagerAdapter(FragmentManager manager) {
+ super(manager);
+ }
+
+ @Override
+ public Fragment getItem(int position) {
+ return mFragmentList.get(position);
+ }
+
+ @Override
+ public int getCount() {
+ return mFragmentList.size();
+ }
+
+ public void addFragment(Fragment fragment, String title) {
+ mFragmentList.add(fragment);
+ mFragmentTitleList.add(title);
+ }
+
+ @Override
+ public CharSequence getPageTitle(int position) {
+ return mFragmentTitleList.get(position);
+ }
+ }
+ /*
+ @Subscribe
+ public void onEvent(EventBusMSGNormal msg) {
+ switch (msg.MSGType) {
+ case EventBusMSG.TRACKLIST_SELECT:
+ case EventBusMSG.TRACKLIST_DESELECT:
+ ActivateActionModeIfNeeded();
+ }
+ }
+ */
+ @Subscribe
+ public void onEvent(Short msg) {
+ switch (msg) {
+ case EventBusMSG.REQUEST_ADD_PLACEMARK:
+ // Show Placemark Dialog
+ FragmentManager fm = getSupportFragmentManager();
+ //FragmentPlacemarkDialog placemarkDialog = new FragmentPlacemarkDialog();
+ //placemarkDialog.show(fm, "");
+ break;
+ case EventBusMSG.UPDATE_TRACKLIST:
+ case EventBusMSG.NOTIFY_TRACKS_DELETED:
+ ActivateActionModeIfNeeded();
+ break;
+ case EventBusMSG.APPLY_SETTINGS:
+ LoadPreferences();
+ break;
+
+ case EventBusMSG.UPDATE_TRACK:
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (menutrackfinished != null)
+ menutrackfinished.setVisible(!GPSApp.getCurrentTrack().getName().equals(""));
+ }
+ });
+ break;
+ case EventBusMSG.TOAST_TRACK_EXPORTED:
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(context, getString(R.string.toast_track_exported), Toast.LENGTH_LONG).show();
+ }
+ });
+ break;
+ case EventBusMSG.TOAST_STORAGE_PERMISSION_REQUIRED:
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(context, getString(R.string.please_grant_storage_permission), Toast.LENGTH_LONG).show();
+ }
+ });
+ break;
+ case EventBusMSG.TOAST_UNABLE_TO_WRITE_THE_FILE:
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(context, getString(R.string.export_unable_to_write_file), Toast.LENGTH_LONG).show();
+ }
+ });
+ }
+ }
+
+ private void LoadPreferences() {
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
+ prefKeepScreenOn = preferences.getBoolean("prefKeepScreenOn", true);
+ if (prefKeepScreenOn) getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ else getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+
+ private void ShutdownApp()
+ {
+ if ((GPSApp.getCurrentTrack().getNumberOfLocations() > 0)
+ || (GPSApp.getCurrentTrack().getNumberOfPlacemarks() > 0)
+ || (GPSApp.getRecording())
+ || (GPSApp.getPlacemarkRequest())) {
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(new ContextThemeWrapper(this, R.style.StyledDialog));
+ builder.setMessage(getResources().getString(R.string.message_exit_finalizing));
+ builder.setIcon(android.R.drawable.ic_menu_info_details);
+ builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ GPSApp.setRecording(false);
+ GPSApp.setPlacemarkRequest(false);
+ EventBus.getDefault().post(EventBusMSG.NEW_TRACK);
+ GPSApp.StopAndUnbindGPSService();
+ GPSApp.setLocationPermissionChecked(false);
+
+ dialog.dismiss();
+ GPSApp.setJustStarted(true);
+ finish();
+ }
+ });
+ builder.setNeutralButton(R.string.cancel, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.dismiss();
+ }
+ });
+ builder.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ GPSApp.setRecording(false);
+ GPSApp.setPlacemarkRequest(false);
+ GPSApp.StopAndUnbindGPSService();
+ GPSApp.setLocationPermissionChecked(false);
+
+ dialog.dismiss();
+ GPSApp.setJustStarted(true);
+ finish();
+ }
+ });
+ AlertDialog dialog = builder.create();
+ dialog.show();
+ } else {
+ GPSApp.setRecording(false);
+ GPSApp.setPlacemarkRequest(false);
+ GPSApp.StopAndUnbindGPSService();
+ GPSApp.setLocationPermissionChecked(false);
+
+ finish();
+ }
+ }
+
+ private void ActivateActionModeIfNeeded() {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if ((GPSApp.getNumberOfSelectedTracks() > 0) && (activeTab == 2)) {
+ if (actionMode == null)
+ // actionMode = (startSupportActionMode(new ToolbarActionMode()));
+ actionMode.setTitle(GPSApp.getNumberOfSelectedTracks() > 1 ? String.valueOf(GPSApp.getNumberOfSelectedTracks()) : "");
+ } else if (actionMode != null) {
+ actionMode.finish();
+ actionMode = null;
+ }
+ }
+ });
+ }
+
+
+ public boolean CheckLocationPermission() {
+ Log.w("myApp", "[#] GPSActivity.java - Check Location Permission...");
+ if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
+ Log.w("myApp", "[#] GPSActivity.java - Location Permission granted");
+ return true; // Permission Granted
+ } else {
+ Log.w("myApp", "[#] GPSActivity.java - Location Permission denied");
+ boolean showRationale = ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_FINE_LOCATION);
+ if (showRationale || !GPSApp.isLocationPermissionChecked()) {
+ Log.w("myApp", "[#] GPSActivity.java - Location Permission denied, need new check");
+ List listPermissionsNeeded = new ArrayList<>();
+ listPermissionsNeeded.add(Manifest.permission.ACCESS_FINE_LOCATION);
+ ActivityCompat.requestPermissions(this, listPermissionsNeeded.toArray(new String[listPermissionsNeeded.size()]) , REQUEST_ID_MULTIPLE_PERMISSIONS);
+ }
+ return false;
+ }
+ }
+
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
+ switch (requestCode) {
+ case REQUEST_ID_MULTIPLE_PERMISSIONS: {
+ Map perms = new HashMap<>();
+
+ if (grantResults.length > 0) {
+ // Fill with actual results from user
+ for (int i = 0; i < permissions.length; i++) perms.put(permissions[i], grantResults[i]);
+ // Check for permissions
+ if (perms.containsKey(Manifest.permission.ACCESS_FINE_LOCATION)) {
+ if (perms.get(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
+ Log.w("myApp", "[#] GPSActivity.java - ACCESS_FINE_LOCATION = PERMISSION_GRANTED; setGPSLocationUpdates!");
+ GPSApp.setGPSLocationUpdates(false);
+ GPSApp.setGPSLocationUpdates(true);
+ GPSApp.updateGPSLocationFrequency();
+ } else {
+ Log.w("myApp", "[#] GPSActivity.java - ACCESS_FINE_LOCATION = PERMISSION_DENIED");
+ }
+ }
+
+ if (perms.containsKey(Manifest.permission.INTERNET)) {
+ if (perms.get(Manifest.permission.INTERNET) == PackageManager.PERMISSION_GRANTED) {
+ Log.w("myApp", "[#] GPSActivity.java - INTERNET = PERMISSION_GRANTED");
+ } else {
+ Log.w("myApp", "[#] GPSActivity.java - INTERNET = PERMISSION_DENIED");
+ }
+ }
+
+ if (perms.containsKey(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
+ if (perms.get(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
+ Log.w("myApp", "[#] GPSActivity.java - WRITE_EXTERNAL_STORAGE = PERMISSION_GRANTED");
+ // ---------------------------------------------------- Create the Directories if not exist
+ File sd = new File(Environment.getExternalStorageDirectory() + "/GPSLogger");
+ if (!sd.exists()) {
+ sd.mkdir();
+ }
+ sd = new File(Environment.getExternalStorageDirectory() + "/GPSLogger/AppData");
+ if (!sd.exists()) {
+ sd.mkdir();
+ }
+ sd = new File(getApplicationContext().getFilesDir() + "/Thumbnails");
+ if (!sd.exists()) {
+ sd.mkdir();
+ }
+ EGM96 egm96 = EGM96.getInstance();
+ if (egm96 != null) {
+ if (!egm96.isEGMGridLoaded()) {
+ //Log.w("myApp", "[#] GPSApplication.java - Loading EGM Grid...");
+ egm96.LoadGridFromFile(Environment.getExternalStorageDirectory() + "/GPSLogger/AppData/WW15MGH.DAC", getApplicationContext().getFilesDir() + "/WW15MGH.DAC");
+ }
+ }
+
+ if (GPSApp.getJobsPending() > 0) GPSApp.ExecuteJob();
+
+ } else {
+ Log.w("myApp", "[#] GPSActivity.java - WRITE_EXTERNAL_STORAGE = PERMISSION_DENIED");
+ if (GPSApp.getJobsPending() > 0) {
+ // Shows toast "Unable to write the file"
+ show_toast_grant_storage_permission = true;
+ EventBus.getDefault().post(EventBusMSG.TOAST_STORAGE_PERMISSION_REQUIRED);
+ GPSApp.setJobsPending(0);
+ }
+ }
+ }
+ }
+ break;
+ }
+ default: {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/Satellite/GPSActivity_sat.java b/app/src/main/java/Satellite/GPSActivity_sat.java
new file mode 100644
index 0000000..6a29428
--- /dev/null
+++ b/app/src/main/java/Satellite/GPSActivity_sat.java
@@ -0,0 +1,133 @@
+package Satellite;
+
+
+import android.Manifest;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.location.Address;
+import android.location.Geocoder;
+import android.location.Location;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.ActivityCompat;
+import android.support.v7.app.AppCompatActivity;
+import android.text.Html;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+import com.google.android.gms.location.FusedLocationProviderClient;
+import com.google.android.gms.location.LocationServices;
+import com.google.android.gms.tasks.OnCompleteListener;
+import com.google.android.gms.tasks.Task;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Locale;
+
+import fr.geolabs.dev.mapmint4me.R;
+
+public class GPSActivity_sat extends AppCompatActivity {
+
+ Button btLocation,btLocation1;
+ TextView textView1, textView2, textView3, textView4, textView5;
+ FusedLocationProviderClient fusedLocationProviderClient;
+
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.layout);
+
+ btLocation = findViewById(R.id.bt_location);
+ btLocation1 = findViewById(R.id.bt_location1);
+ textView1 = findViewById(R.id.text_view1);
+ textView2 = findViewById(R.id.text_view2);
+ textView3 = findViewById(R.id.text_view3);
+ textView4 = findViewById(R.id.text_view4);
+ textView5 = findViewById(R.id.text_view5);
+
+ fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this);
+
+ btLocation.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+
+ if (ActivityCompat.checkSelfPermission(GPSActivity_sat.this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
+ getLocation();
+ } else {
+ ActivityCompat.requestPermissions(GPSActivity_sat.this,
+ new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 44);
+ }
+
+ }
+ });
+
+ btLocation1.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent intent = new Intent(GPSActivity_sat.this, GPSActivity.class);
+ startActivity(intent);
+ }
+ });
+
+ }
+
+ private void getLocation() {
+
+ if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
+ // TODO: Consider calling
+ // ActivityCompat#requestPermissions
+ // here to request the missing permissions, and then overriding
+ // public void onRequestPermissionsResult(int requestCode, String[] permissions,
+ // int[] grantResults)
+ // to handle the case where the user grants the permission. See the documentation
+ // for ActivityCompat#requestPermissions for more details.
+ return;
+ }
+ fusedLocationProviderClient.getLastLocation().addOnCompleteListener(new OnCompleteListener() {
+ @Override
+ public void onComplete(@NonNull Task task) {
+ Location location = task.getResult();
+ if (location != null) {
+ try {
+ Geocoder geocoder = new Geocoder(GPSActivity_sat.this,
+ Locale.getDefault());
+
+ List addresses = geocoder.getFromLocation(
+ location.getLatitude(), location.getLongitude(), 1
+ );
+ textView1.setText(Html.fromHtml(
+ "lattitude: "
+ + addresses.get(0).getLatitude()
+ ));
+
+ textView2.setText(Html.fromHtml(
+ "longitude: "
+ + addresses.get(0).getLongitude()
+ ));
+
+ textView3.setText(Html.fromHtml(
+ "Country Name: "
+ + addresses.get(0).getCountryName()
+ ));
+
+ textView4.setText(Html.fromHtml(
+ "Locality: "
+ + addresses.get(0).getLocality()
+ ));
+
+ textView5.setText(Html.fromHtml(
+ "Address: "
+ + addresses.get(0).getAddressLine(0)
+ ));
+
+
+ } catch (IOException e) {
+ }
+ }
+ }
+ });
+
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/Satellite/GPSApplication.java b/app/src/main/java/Satellite/GPSApplication.java
new file mode 100644
index 0000000..75bd16e
--- /dev/null
+++ b/app/src/main/java/Satellite/GPSApplication.java
@@ -0,0 +1,1592 @@
+
+package Satellite;
+
+import android.Manifest;
+import android.app.Application;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.location.GpsSatellite;
+import android.location.GpsStatus;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.location.LocationProvider;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.StrictMode;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.app.AppCompatDelegate;
+import android.support.v7.preference.PreferenceManager;
+import android.util.Log;
+
+import org.greenrobot.eventbus.EventBus;
+import org.greenrobot.eventbus.Subscribe;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.StringTokenizer;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import fr.geolabs.dev.mapmint4me.R;
+
+
+
+public class GPSApplication extends Application implements GpsStatus.Listener, LocationListener {
+
+ //private static final float M_TO_FT = 3.280839895f;
+ private static final int NOT_AVAILABLE = -100000;
+
+ //private static final int UM_METRIC_MS = 0;
+ private static final int UM_METRIC_KMH = 1;
+ //private static final int UM_IMPERIAL_FPS = 8;
+ //private static final int UM_IMPERIAL_MPH = 9;
+
+ private static final int STABILIZERVALUE = 3000; // The application discards fixes for 3000 ms (minimum)
+ private static final int DEFAULTHANDLERTIMER = 5000; // The timer for turning off GPS on exit
+ private static final int GPSUNAVAILABLEHANDLERTIMER = 7000; // The "GPS temporary unavailable" timer
+ private int StabilizingSamples = 3;
+
+ private static final int GPS_DISABLED = 0;
+ private static final int GPS_OUTOFSERVICE = 1;
+ private static final int GPS_TEMPORARYUNAVAILABLE = 2;
+ private static final int GPS_SEARCHING = 3;
+ private static final int GPS_STABILIZING = 4;
+ private static final int GPS_OK = 5;
+
+ public static final int APP_ORIGIN_NOT_SPECIFIED = 0;
+ public static final int APP_ORIGIN_GOOGLE_PLAY_STORE = 1; // The app is installed via the Google Play Store
+
+ public static final int JOB_TYPE_NONE = 0; // No operation
+ public static final int JOB_TYPE_EXPORT = 1; // Bulk Exportation
+ public static final int JOB_TYPE_VIEW = 2; // Bulk View
+ public static final int JOB_TYPE_SHARE = 3; // Bulk Share
+ public static final int JOB_TYPE_DELETE = 4; // Bulk Delete
+
+ public static final String FLAG_RECORDING = "flagRecording"; // The persistent Flag is set when the app is recording, in order to detect Background Crashes
+
+
+ // Preferences Variables
+ // private boolean prefKeepScreenOn = true; // DONE in GPSActivity
+ private boolean prefShowDecimalCoordinates = false;
+ private int prefViewTracksWith = 0;
+ private int prefUM = UM_METRIC_KMH;
+ private float prefGPSdistance = 0f;
+ private long prefGPSupdatefrequency = 1000L;
+ private boolean prefEGM96AltitudeCorrection = false;
+ private double prefAltitudeCorrection = 0d;
+ private boolean prefExportKML = true;
+ private boolean prefExportGPX = true;
+ private int prefGPXVersion = 100; // the version of the GPX schema
+ private boolean prefExportTXT = false;
+ private int prefKMLAltitudeMode = 0;
+ private int prefShowTrackStatsType = 0;
+ private int prefShowDirections = 0;
+ private boolean prefGPSWeekRolloverCorrected= false;
+
+ private boolean LocationPermissionChecked = false; // If the flag is false the GPSActivity will check for Location Permission
+ private boolean isFirstRun = false; // True if it is the first run of the app (the DB is empty)
+ private boolean isJustStarted = true; // True if the application has just been started
+ private boolean isMockProvider = false; // True if the location is from mock provider
+
+ private LocationExtended PrevFix = null;
+ private boolean isPrevFixRecorded = false;
+
+ private LocationExtended PrevRecordedFix = null;
+
+ private boolean MustUpdatePrefs = true; // True if preferences needs to be updated
+
+ private boolean isCurrentTrackVisible = false;
+ private boolean isContextMenuShareVisible = false; // True if "Share with ..." menu is visible
+ private boolean isContextMenuViewVisible = false; // True if "View in *" menu is visible
+ private String ViewInApp = ""; // The string of default app name for "View"
+ private String _satelliteList = "";
+ // "" in case of selector
+
+ // Singleton instance
+ private static GPSApplication singleton;
+ public static GPSApplication getInstance(){
+ return singleton;
+ }
+
+
+ DatabaseHandler GPSDataBase;
+ private String PlacemarkDescription = "";
+ private boolean Recording = false;
+ private boolean PlacemarkRequest = false;
+ private boolean isGPSLocationUpdatesActive = false;
+ private int GPSStatus = GPS_SEARCHING;
+
+ private int AppOrigin = APP_ORIGIN_NOT_SPECIFIED; // Which package manager is used to install this app
+
+ private boolean NewTrackFlag = false; // The variable that handle the double-click on "Track Finished"
+ final Handler newtrackhandler = new Handler();
+ Runnable newtrackr = new Runnable() {
+ @Override
+ public void run() {
+ NewTrackFlag = false;
+ }
+ };
+
+ private boolean LocationSettingsFlag = false; // The variable that handle the double-click on "Open Location Settings"
+ final Handler locationsettingshandler = new Handler();
+ Runnable locationsettingsr = new Runnable() {
+ @Override
+ public void run() {
+ LocationSettingsFlag = false;
+ }
+ };
+
+ private LocationManager mlocManager = null; // GPS LocationManager
+ private int _NumberOfSatellites = 0;
+ private int _NumberOfSatellitesUsedInFix = 0;
+
+ private int GPSActivity_activeTab = 0; // The active tab on GPSActivity
+ private int JobProgress = 0;
+ private int JobsPending = 0; // The number of jobs to be done
+ public int JobType = JOB_TYPE_NONE; // The type of job that is pending
+ private boolean DeleteAlsoExportedFiles = false; // When true, the deletion of some tracks will delete also the exported files of the tracks
+
+ public int GPSActivityColorTheme;
+
+ private int _Stabilizer = StabilizingSamples;
+ private int HandlerTimer = DEFAULTHANDLERTIMER;
+
+ private LocationExtended _currentLocationExtended = null;
+ private LocationExtended _currentPlacemark = null;
+ private Track _currentTrack = null;
+ private List _ArrayListTracks = Collections.synchronizedList(new ArrayList());
+
+ Thumbnailer Th;
+ //Exporter Ex;
+ private AsyncUpdateThreadClass asyncUpdateThread = new AsyncUpdateThreadClass();
+
+ // The handler that switches off the location updates after a time delay:
+ final Handler handler = new Handler();
+ Runnable r = new Runnable() {
+
+ @Override
+ public void run() {
+ setGPSLocationUpdates(false);
+ }
+ };
+
+ final Handler gpsunavailablehandler = new Handler();
+ Runnable unavailr = new Runnable() {
+
+ @Override
+ public void run() {
+ if ((GPSStatus == GPS_OK) || (GPSStatus == GPS_STABILIZING)) {
+ GPSStatus = GPS_TEMPORARYUNAVAILABLE;
+ EventBus.getDefault().post(EventBusMSG.UPDATE_FIX);
+ }
+ }
+ };
+
+ private final int MAX_ACTIVE_EXPORTER_THREADS = 3; // The maximum number of Exporter threads to run simultaneously
+
+ private List ExportingTaskList = new ArrayList<>();
+
+
+ // The handler that checks the progress of an exportation:
+ private final int ExportingStatusCheckInterval = 16; // The app updates the progress of exportation every 16 milliseconds
+ final Handler ExportingStatusCheckHandler = new Handler();
+
+ Runnable ExportingStatusChecker = new Runnable() {
+ @Override
+ public void run() {
+ long Total = 0;
+ long Progress = 0;
+ int Exporters_Total = ExportingTaskList.size(); // The total amount of exportation into the current job
+ int Exporters_Pending = 0;
+ int Exporters_Running = 0; // The amount of exportation in progress
+ int Exporters_Success = 0; // The amount of exportation finished with success
+ int Exporters_Failed = 0; // The amount of exportation failed
+
+
+ // Check Progress
+ for (ExportingTask ET : ExportingTaskList) {
+ Total += ET.getNumberOfPoints_Total();
+ Progress += ET.getNumberOfPoints_Processed();
+ if (ET.getStatus() == ExportingTask.STATUS_PENDING) Exporters_Pending++;
+ if (ET.getStatus() == ExportingTask.STATUS_RUNNING) Exporters_Running++;
+ if (ET.getStatus() == ExportingTask.STATUS_ENDED_SUCCESS) Exporters_Success++;
+ if (ET.getStatus() == ExportingTask.STATUS_ENDED_FAILED) Exporters_Failed++;
+ }
+
+ // Update job progress
+ if (Total != 0) {
+ if (JobProgress != (int) Math.round(1000L * Progress / Total)) { // The ProgressBar on FragmentJobProgress has android:max="1000"
+ JobProgress = (int) Math.round(1000L * Progress / Total);
+ EventBus.getDefault().post(EventBusMSG.UPDATE_JOB_PROGRESS);
+ }
+ } else {
+ if (JobProgress != 0) {
+ JobProgress = 0;
+ EventBus.getDefault().post(EventBusMSG.UPDATE_JOB_PROGRESS);
+ }
+ }
+
+ //Log.w("myApp", "[#] GPSApplication.java - ExportingStatusChecker running: " + 100*Progress/Total + "% - P "
+ // + Exporters_Pending + " - R " + Exporters_Running + " - S " + Exporters_Success + " - F " + Exporters_Failed);
+
+ // Exportation Failed
+ if (Exporters_Failed != 0) {
+ EventBus.getDefault().post(EventBusMSG.TOAST_UNABLE_TO_WRITE_THE_FILE);
+ JobProgress = 0;
+ JobsPending = 0;
+ EventBus.getDefault().post(EventBusMSG.UPDATE_JOB_PROGRESS);
+ return;
+ }
+
+ // Exportation Finished
+ if (Exporters_Success == Exporters_Total) {
+ if (JobType == JOB_TYPE_VIEW) {
+ if (!ExportingTaskList.isEmpty()) ViewTrack(ExportingTaskList.get(0));
+ } else if (JobType == JOB_TYPE_SHARE) {
+ EventBus.getDefault().post(EventBusMSG.INTENT_SEND);
+ } else {
+ EventBus.getDefault().post(EventBusMSG.TOAST_TRACK_EXPORTED);
+ }
+ JobProgress = 0;
+ JobsPending = 0;
+ EventBus.getDefault().post(EventBusMSG.UPDATE_JOB_PROGRESS);
+ return;
+ }
+
+ // If needed, run another Exportation Thread
+ if ((Exporters_Running < MAX_ACTIVE_EXPORTER_THREADS) && (Exporters_Pending > 0)) {
+ for (ExportingTask ET : ExportingTaskList) {
+ if (ET.getStatus() == ExportingTask.STATUS_PENDING) {
+ //Log.w("myApp", "[#] GPSApplication.java - Run the export thread nr." + Exporters_Running + ": " + ET.getId());
+ ET.setStatus(ExportingTask.STATUS_RUNNING);
+ //ExecuteExportingTask(ET);
+ break;
+ }
+ }
+ }
+
+ ExportingStatusCheckHandler.postDelayed(ExportingStatusChecker, ExportingStatusCheckInterval);
+ }
+ };
+
+ void startExportingStatusChecker() {
+ ExportingStatusChecker.run();
+ }
+
+ void stopExportingStatusChecker() {
+ ExportingStatusCheckHandler.removeCallbacks(ExportingStatusChecker);
+ }
+
+
+ // ------------------------------------------------------------------------------------ Service
+ Intent GPSServiceIntent;
+ GPSService GPSLoggerService;
+ boolean isGPSServiceBound = false;
+
+ private ServiceConnection GPSServiceConnection = new ServiceConnection() {
+
+ @Override
+ public void onServiceConnected(ComponentName className,
+ IBinder service) {
+ GPSService.LocalBinder binder = (GPSService.LocalBinder) service;
+ GPSLoggerService = binder.getServiceInstance(); //Get instance of your service!
+ Log.w("myApp", "[#] GPSApplication.java - GPSSERVICE CONNECTED - onServiceConnected event");
+ isGPSServiceBound = true;
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName arg0) {
+ Log.w("myApp", "[#] GPSApplication.java - GPSSERVICE DISCONNECTED - onServiceDisconnected event");
+ isGPSServiceBound = false;
+ }
+ };
+
+ private void StartAndBindGPSService() {
+ GPSServiceIntent = new Intent(GPSApplication.this, GPSService.class);
+ //Start the service
+ startService(GPSServiceIntent);
+ //Bind to the service
+ if (Build.VERSION.SDK_INT >= 14)
+ bindService(GPSServiceIntent, GPSServiceConnection, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT);
+ else
+ bindService(GPSServiceIntent, GPSServiceConnection, Context.BIND_AUTO_CREATE);
+ Log.w("myApp", "[#] GPSApplication.java - StartAndBindGPSService");
+ }
+
+
+ /* private void UnbindGPSService() { //UNUSED
+ try {
+ unbindService(GPSServiceConnection); //Unbind to the service
+ Log.w("myApp", "[#] GPSApplication.java - Service unbound");
+ } catch (Exception e) {
+ Log.w("myApp", "[#] GPSApplication.java - Unable to unbind the GPSService");
+ }
+ } */
+
+ public void StopAndUnbindGPSService() {
+ try {
+ unbindService(GPSServiceConnection); //Unbind to the service
+ Log.w("myApp", "[#] GPSApplication.java - Service unbound");
+ } catch (Exception e) {
+ Log.w("myApp", "[#] GPSApplication.java - Unable to unbind the GPSService");
+ }
+ try {
+ stopService(GPSServiceIntent); //Stop the service
+ Log.w("myApp", "[#] GPSApplication.java - Service stopped");
+ } catch (Exception e) {
+ Log.w("myApp", "[#] GPSApplication.java - Unable to stop GPSService");
+ }
+ }
+
+
+ // ------------------------------------------------------------------------ Getters and Setters
+ public boolean getNewTrackFlag() {
+ return NewTrackFlag;
+ }
+
+ public void setNewTrackFlag(boolean newTrackFlag) {
+ if (newTrackFlag) {
+ NewTrackFlag = true;
+ newtrackhandler.removeCallbacks(newtrackr); // Cancel the previous newtrackr handler
+ newtrackhandler.postDelayed(newtrackr, 1500); // starts the new handler
+ } else {
+ NewTrackFlag = false;
+ newtrackhandler.removeCallbacks(newtrackr); // Cancel the previous newtrackr handler
+ }
+ }
+
+ public boolean getLocationSettingsFlag() {
+ return LocationSettingsFlag;
+ }
+
+ public void setLocationSettingsFlag(boolean locationSettingsFlag) {
+ if (locationSettingsFlag) {
+ LocationSettingsFlag = true;
+ locationsettingshandler.removeCallbacks(locationsettingsr); // Cancel the previous locationsettingsr handler
+ locationsettingshandler.postDelayed(locationsettingsr, 1000); // starts the new handler
+ } else {
+ LocationSettingsFlag = false;
+ locationsettingshandler.removeCallbacks(locationsettingsr); // Cancel the previous locationsettingsr handler
+ }
+ }
+
+ public boolean isLocationPermissionChecked() {
+ return LocationPermissionChecked;
+ }
+
+ public void setLocationPermissionChecked(boolean locationPermissionChecked) {
+ LocationPermissionChecked = locationPermissionChecked;
+ }
+
+ public void setHandlerTimer(int handlerTimer) {
+ HandlerTimer = handlerTimer;
+ }
+
+ public int getHandlerTimer() {
+ return HandlerTimer;
+ }
+
+ public int getGPSStatus() {
+ return GPSStatus;
+ }
+
+
+
+ public double getPrefAltitudeCorrection() {
+ return prefAltitudeCorrection;
+ }
+
+ public boolean getPrefEGM96AltitudeCorrection() {
+ return prefEGM96AltitudeCorrection;
+ }
+
+ public boolean getPrefShowDecimalCoordinates() {
+ return prefShowDecimalCoordinates;
+ }
+
+
+ public int getPrefUM() {
+ return prefUM;
+ }
+
+ public int getPrefShowTrackStatsType() {
+ return prefShowTrackStatsType;
+ }
+
+ public int getPrefShowDirections() {
+ return prefShowDirections;
+ }
+
+ public LocationExtended getCurrentLocationExtended() {
+ return _currentLocationExtended;
+ }
+
+ public void setPlacemarkDescription(String Description) {
+ this.PlacemarkDescription = Description;
+ }
+
+ public Track getCurrentTrack() {
+ return _currentTrack;
+ }
+
+ public int getNumberOfSatellites() {
+ return _NumberOfSatellites;
+ }
+
+ public String get_satelliteList() {
+ return _satelliteList;
+ }
+
+ public int getNumberOfSatellitesUsedInFix() {
+ return _NumberOfSatellitesUsedInFix;
+ }
+
+ public boolean getRecording() {
+ return Recording;
+ }
+
+ public void setRecording(boolean recordingState) {
+ PrevRecordedFix = null;
+ Recording = recordingState;
+ if (Recording) FlagAdd(FLAG_RECORDING);
+ else FlagRemove(FLAG_RECORDING);
+ }
+
+ public boolean getPlacemarkRequest() { return PlacemarkRequest; }
+
+ public void setPlacemarkRequest(boolean placemarkRequest) { PlacemarkRequest = placemarkRequest; }
+
+ public List getTrackList() {
+ return _ArrayListTracks;
+ }
+
+ public boolean isCurrentTrackVisible() {
+ return isCurrentTrackVisible;
+ }
+
+ public void setisCurrentTrackVisible(boolean currentTrackVisible) {
+ isCurrentTrackVisible = currentTrackVisible;
+ }
+
+ public int getAppOrigin() {
+ return AppOrigin;
+ }
+
+ public int getJobProgress() {
+ return JobProgress;
+ }
+
+ public int getJobsPending() {
+ return JobsPending;
+ }
+
+ public void setJobsPending(int jobsPending) {
+ JobsPending = jobsPending;
+ }
+
+ public int getGPSActivity_activeTab() {
+ return GPSActivity_activeTab;
+ }
+
+ public void setGPSActivity_activeTab(int GPSActivity_activeTab) {
+ System.out.println(GPSActivity_activeTab);
+ this.GPSActivity_activeTab = 1;
+ }
+
+ public List getExportingTaskList() {
+ return ExportingTaskList;
+ }
+
+ public void setDeleteAlsoExportedFiles(boolean deleteAlsoExportedFiles) {
+ DeleteAlsoExportedFiles = deleteAlsoExportedFiles;
+ }
+
+ public boolean isJustStarted() {
+ return isJustStarted;
+ }
+
+ public void setJustStarted(boolean justStarted) {
+ isJustStarted = justStarted;
+ }
+
+ // ------------------------------------------------------------------------ Utility
+
+ private void DeleteFile(String filename) {
+ File file = new File(filename);
+ boolean deleted;
+ if (file.exists ()) {
+ deleted = file.delete();
+ if (deleted) Log.w("myApp", "[#] GPSApplication.java - DeleteFile: " + filename + " deleted");
+ else Log.w("myApp", "[#] GPSApplication.java - DeleteFile: " + filename + " unable to delete the File");
+ }
+ else Log.w("myApp", "[#] GPSApplication.java - DeleteFile: " + filename + " doesn't exists");
+ }
+
+
+ /* NOT USED, Commented out
+ private boolean FileExists(String filename) {
+ File file = new File(filename);
+ return file.exists ();
+ } */
+
+
+ // Flags are Boolean SharedPreferences that are excluded by automatic Backups
+
+ public void FlagAdd (String flag) {
+ SharedPreferences preferences_nobackup = getSharedPreferences("prefs_nobackup",Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = preferences_nobackup.edit();
+ editor.putBoolean(flag, true);
+ editor.commit();
+ }
+
+
+ public void FlagRemove (String flag) {
+ SharedPreferences preferences_nobackup = getSharedPreferences("prefs_nobackup",Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = preferences_nobackup.edit();
+ editor.remove(flag);
+ editor.commit();
+ }
+
+
+ public boolean FlagExists (String flag) {
+ SharedPreferences preferences_nobackup = getSharedPreferences("prefs_nobackup",Context.MODE_PRIVATE);
+ return preferences_nobackup.getBoolean(flag, false);
+ }
+
+
+ // --------------------------------------------------------------------------------------------
+
+ @Override
+ public void onCreate() {
+
+ AppCompatDelegate.setDefaultNightMode(Integer.valueOf(PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).getString("prefColorTheme", "2")));
+
+ super.onCreate();
+
+ singleton = this;
+
+ // work around the android.os.FileUriExposedException
+ StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
+ StrictMode.setVmPolicy(builder.build());
+
+ final String CHANNEL_ID = "GPSLoggerServiceChannel";
+
+ // Create notification channel for Android >= O
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ NotificationChannel channel = new NotificationChannel(
+ CHANNEL_ID,
+ getString(R.string.app_name),
+ NotificationManager.IMPORTANCE_LOW
+ );
+ channel.setSound(null, null);
+ channel.enableLights(false);
+ channel.enableVibration(false);
+ channel.setSound(null,null);
+
+ NotificationManager manager = getSystemService(NotificationManager.class);
+ manager.createNotificationChannel(channel);
+ }
+
+ // -----------------------
+ // TODO: Uncomment it to run the Week Rollover Tests (For Test Purpose)
+ // SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).edit();
+ // editor.putBoolean("prefGPSWeekRolloverCorrected", false);
+ // editor.commit();
+ // -----------------------
+
+ // -----------------------
+ // TODO: Uncomment it to reload the EGM Grid File (For Test Purpose)
+ //File file = new File(getApplicationContext().getFilesDir() + "/WW15MGH.DAC");
+ //if (file.exists ()) file.delete();
+ // -----------------------
+
+ EventBus.getDefault().register(this);
+
+ mlocManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); // Location Manager
+
+ File sd = new File(Environment.getExternalStorageDirectory() + "/GPSLogger"); // Create the Directories if not exist
+ if (!sd.exists()) {
+ sd.mkdir();
+ Log.w("myApp", "[#] GPSApplication.java - Folder created: " + sd.getAbsolutePath());
+ }
+ sd = new File(Environment.getExternalStorageDirectory() + "/GPSLogger/AppData");
+ if (!sd.exists()) {
+ sd.mkdir();
+ Log.w("myApp", "[#] GPSApplication.java - Folder created: " + sd.getAbsolutePath());
+ }
+
+ sd = new File(getApplicationContext().getFilesDir() + "/Thumbnails");
+ if (!sd.exists()) {
+ sd.mkdir();
+ Log.w("myApp", "[#] GPSApplication.java - Folder created: " + sd.getAbsolutePath());
+ }
+
+ EGM96 egm96 = EGM96.getInstance(); // Load EGM Grid
+ if (egm96 != null) {
+ if (!egm96.isEGMGridLoaded()) {
+ egm96.LoadGridFromFile(Environment.getExternalStorageDirectory() + "/GPSLogger/AppData/WW15MGH.DAC", getApplicationContext().getFilesDir() + "/WW15MGH.DAC");
+ }
+ }
+
+ try { // Determine the app installation source
+ String installer;
+ installer = getApplicationContext().getPackageManager().getInstallerPackageName(getApplicationContext().getPackageName());
+ if (installer.equals("com.android.vending") || installer.equals("com.google.android.feedback"))
+ AppOrigin = APP_ORIGIN_GOOGLE_PLAY_STORE; // App installed from Google Play Store
+ else AppOrigin = APP_ORIGIN_NOT_SPECIFIED; // Otherwise
+ } catch (Throwable e) {
+ Log.w("myApp", "[#] GPSApplication.java - Exception trying to determine the package installer");
+ AppOrigin = APP_ORIGIN_NOT_SPECIFIED;
+ }
+
+ GPSDataBase = new DatabaseHandler(this); // Initialize the Database
+
+ // Prepare the current track
+ if (GPSDataBase.getLastTrackID() == 0) {
+ GPSDataBase.addTrack(new Track()); // Creation of the first track if the DB is empty
+ isFirstRun = true;
+ }
+ _currentTrack = GPSDataBase.getLastTrack(); // Get the last track
+
+ LoadPreferences(); // Load Settings
+
+ // ----------------------------------------------------------------------------------------
+
+ asyncUpdateThread.start();
+
+ // Get max available VM memory, exceeding this amount will throw an
+ // OutOfMemory exception. Stored in kilobytes as LruCache takes an
+ // int in its constructor.
+ //Log.w("myApp", "[#] GPSApplication.java - Max available VM memory = " + (int) (Runtime.getRuntime().maxMemory() / 1024) + " kbytes");
+ }
+
+
+ @Override
+ public void onTerminate() {
+ Log.w("myApp", "[#] GPSApplication.java - onTerminate");
+ EventBus.getDefault().unregister(this);
+ StopAndUnbindGPSService();
+ super.onTerminate();
+ }
+
+
+ @Subscribe
+ public void onEvent(Short msg) {
+ if (msg == EventBusMSG.NEW_TRACK) {
+ AsyncTODO ast = new AsyncTODO();
+ ast.TaskType = "TASK_NEWTRACK";
+ ast.location = null;
+ AsyncTODOQueue.add(ast);
+ return;
+ }
+ if (msg == EventBusMSG.ADD_PLACEMARK) {
+ AsyncTODO ast = new AsyncTODO();
+ ast.TaskType = "TASK_ADDPLACEMARK";
+ ast.location = _currentPlacemark;
+ _currentPlacemark.setDescription(PlacemarkDescription);
+ AsyncTODOQueue.add(ast);
+ return;
+ }
+ if (msg == EventBusMSG.APP_PAUSE) {
+ handler.postDelayed(r, getHandlerTimer()); // Starts the switch-off handler (delayed by HandlerTimer)
+ if ((_currentTrack.getNumberOfLocations() == 0) && (_currentTrack.getNumberOfPlacemarks() == 0)
+ && (!Recording) && (!PlacemarkRequest)) StopAndUnbindGPSService();
+ System.gc(); // Clear mem from released objects with Garbage Collector
+ return;
+ }
+ if (msg == EventBusMSG.APP_RESUME) {
+ //Log.w("myApp", "[#] GPSApplication.java - Received EventBusMSG.APP_RESUME");
+ AsyncPrepareTracklistContextMenu asyncPrepareTracklistContextMenu = new AsyncPrepareTracklistContextMenu();
+ asyncPrepareTracklistContextMenu.start();
+ handler.removeCallbacks(r); // Cancel the switch-off handler
+ setHandlerTimer(DEFAULTHANDLERTIMER);
+ setGPSLocationUpdates(true);
+ if (MustUpdatePrefs) {
+ MustUpdatePrefs = false;
+ LoadPreferences();
+ }
+ StartAndBindGPSService();
+ return;
+ }
+ if (msg == EventBusMSG.UPDATE_SETTINGS) {
+ MustUpdatePrefs = true;
+ return;
+ }
+ }
+
+
+ public void setGPSLocationUpdates (boolean state) {
+ // Request permissions = https://developer.android.com/training/permissions/requesting.html
+ if (!state && !getRecording() && isGPSLocationUpdatesActive
+ && (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED)) {
+ GPSStatus = GPS_SEARCHING;
+ mlocManager.removeGpsStatusListener(this);
+ mlocManager.removeUpdates(this);
+ isGPSLocationUpdatesActive = false;
+ //Log.w("myApp", "[#] GPSApplication.java - setGPSLocationUpdates = false");
+ }
+ if (state && !isGPSLocationUpdatesActive
+ && (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED)) {
+ mlocManager.addGpsStatusListener(this);
+ mlocManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, prefGPSupdatefrequency, 0, this); // Requires Location update
+ isGPSLocationUpdatesActive = true;
+ //Log.w("myApp", "[#] GPSApplication.java - setGPSLocationUpdates = true");
+ if (prefGPSupdatefrequency >= 1000) StabilizingSamples = (int) Math.ceil(STABILIZERVALUE / prefGPSupdatefrequency);
+ else StabilizingSamples = (int) Math.ceil(STABILIZERVALUE / 1000);
+ }
+ }
+
+ public void updateGPSLocationFrequency () {
+ if (isGPSLocationUpdatesActive
+ && (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED)) {
+ //Log.w("myApp", "[#] GPSApplication.java - updateGPSLocationFrequency");
+ mlocManager.removeGpsStatusListener(this);
+ mlocManager.removeUpdates(this);
+ if (prefGPSupdatefrequency >= 1000) StabilizingSamples = (int) Math.ceil(STABILIZERVALUE / prefGPSupdatefrequency);
+ else StabilizingSamples = (int) Math.ceil(STABILIZERVALUE / 1000);
+ mlocManager.addGpsStatusListener(this);
+ mlocManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, prefGPSupdatefrequency, 0, this);
+ }
+ }
+
+ public void updateSats() {
+ try {
+ if ((mlocManager != null) && (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED)) {
+ GpsStatus gs = mlocManager.getGpsStatus(null);
+ int sats_inview = 0; // Satellites in view;
+ int sats_used = 0; // Satellites used in fix;
+
+ if (gs != null) {
+ Iterable sats = gs.getSatellites();
+ for (GpsSatellite sat : sats) {
+ sats_inview++;
+
+ if (sat.usedInFix()) {
+ sats_used++;
+ int i =0;
+ if(i<5){
+
+ _satelliteList = _satelliteList+ "PRN No:" +sat.getPrn() + ", SNR: " + sat.getSnr() + ", Azimuth " +sat.getAzimuth()+", Elevation:"+sat.getElevation()+"\n";
+ Log.d("Satellite_info",_satelliteList);
+ i = i+1;
+
+
+ }
+ }
+ //Log.w("myApp", "[#] GPSApplication.java - updateSats: i=" + i);
+ }
+ _NumberOfSatellites = sats_inview;
+ _NumberOfSatellitesUsedInFix = sats_used;
+ } else {
+ _NumberOfSatellites = NOT_AVAILABLE;
+ _NumberOfSatellitesUsedInFix = NOT_AVAILABLE;
+ }
+ } else {
+ _NumberOfSatellites = NOT_AVAILABLE;
+ _NumberOfSatellitesUsedInFix = NOT_AVAILABLE;
+ }
+ } catch (NullPointerException e) {
+ _NumberOfSatellites = NOT_AVAILABLE;
+ _NumberOfSatellitesUsedInFix = NOT_AVAILABLE;
+ //Log.w("myApp", "[#] GPSApplication.java - updateSats: Caught NullPointerException: " + e);
+ }
+ //Log.w("myApp", "[#] GPSApplication.java - updateSats: Total=" + _NumberOfSatellites + " Used=" + _NumberOfSatellitesUsedInFix);
+ }
+
+
+ private void ViewTrack(ExportingTask exportingTask) {
+ if (prefViewTracksWith == 0) { // KML Viewer
+ File file = new File(Environment.getExternalStorageDirectory() + "/GPSLogger/AppData/", exportingTask.getName() + ".kml");
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setDataAndType(Uri.fromFile(file), "application/vnd.google-earth.kml+xml");
+ try {
+ startActivity(intent);
+ } catch (ActivityNotFoundException e) {
+ Log.w("myApp", "[#] GPSApplication.java - ViewTrack: Unable to view the track: " + e);
+ AsyncPrepareTracklistContextMenu asyncPrepareTracklistContextMenu = new AsyncPrepareTracklistContextMenu();
+ asyncPrepareTracklistContextMenu.start();
+ }
+ }
+ if (prefViewTracksWith == 1) { // GPX Viewer
+ File file = new File(Environment.getExternalStorageDirectory() + "/GPSLogger/AppData/", exportingTask.getName() + ".gpx");
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setDataAndType(Uri.fromFile(file), "gpx+xml");
+ try {
+ startActivity(intent);
+ } catch (ActivityNotFoundException e) {
+ Log.w("myApp", "[#] GPSApplication.java - ViewTrack: Unable to view the track: " + e);
+ AsyncPrepareTracklistContextMenu asyncPrepareTracklistContextMenu = new AsyncPrepareTracklistContextMenu();
+ asyncPrepareTracklistContextMenu.start();
+ }
+ }
+ }
+
+
+ public ArrayList getSelectedTracks() {
+ ArrayList selTracks = new ArrayList();
+ synchronized(_ArrayListTracks) {
+ for (Track T : _ArrayListTracks) {
+ if (T.isSelected()) {
+ selTracks.add(T);
+ }
+ }
+ }
+ return (selTracks);
+ }
+
+
+ public int getNumberOfSelectedTracks() {
+ int nsel = 0;
+ synchronized(_ArrayListTracks) {
+ for (Track T : _ArrayListTracks) {
+ if (T.isSelected()) nsel++;
+ }
+ }
+ return nsel;
+ }
+
+
+ public void DeselectAllTracks() {
+ synchronized(_ArrayListTracks) {
+ for (Track T : _ArrayListTracks) {
+ if (T.isSelected()) {
+ T.setSelected(false);
+ // EventBus.getDefault().post(new EventBusMSGNormal(EventBusMSG.TRACKLIST_DESELECT, T.getId()));
+ }
+ }
+ }
+ EventBus.getDefault().post(EventBusMSG.REFRESH_TRACKLIST);
+ }
+
+
+ public void LoadJob (int jobType) {
+ ExportingTaskList.clear();
+ synchronized(_ArrayListTracks) {
+ for (Track T : _ArrayListTracks) {
+ if (T.isSelected()) {
+ ExportingTask ET = new ExportingTask();
+ ET.setId(T.getId());
+ ET.setName(T.getName());
+ ET.setNumberOfPoints_Total(T.getNumberOfLocations() + T.getNumberOfPlacemarks());
+ ET.setNumberOfPoints_Processed(0);
+ ExportingTaskList.add(ET);
+ }
+ }
+ }
+ JobsPending = ExportingTaskList.size();
+ JobType = jobType;
+ }
+
+ /*
+ public void ExecuteExportingTask (ExportingTask exportingTask) {
+ switch (JobType) {
+ case JOB_TYPE_NONE:
+ case JOB_TYPE_DELETE:
+ break;
+ case JOB_TYPE_EXPORT:
+ Ex = new Exporter(exportingTask, prefExportKML, prefExportGPX, prefExportTXT, Environment.getExternalStorageDirectory() + "/GPSLogger");
+ Ex.start();
+ break;
+ case JOB_TYPE_VIEW:
+ if (prefViewTracksWith == 0) Ex = new Exporter(exportingTask, true, false, false, Environment.getExternalStorageDirectory() + "/GPSLogger/AppData");
+ if (prefViewTracksWith == 1) Ex = new Exporter(exportingTask, false, true, false, Environment.getExternalStorageDirectory() + "/GPSLogger/AppData");
+ Ex.start();
+ break;
+ case JOB_TYPE_SHARE:
+ Ex = new Exporter(exportingTask, prefExportKML, prefExportGPX, prefExportTXT, Environment.getExternalStorageDirectory() + "/GPSLogger/AppData");
+ Ex.start();
+ break;
+ default:
+ break;
+ }
+ }
+ */
+ public void ExecuteJob () {
+ if (!ExportingTaskList.isEmpty()) {
+ switch (JobType) {
+ case JOB_TYPE_NONE:
+ break;
+ case JOB_TYPE_DELETE:
+ String S = "TASK_DELETE_TRACKS";
+ for (ExportingTask ET : ExportingTaskList) {
+ S = S + " " + ET.getId();
+ }
+ AsyncTODO ast = new AsyncTODO();
+ ast.TaskType = S;
+ ast.location = null;
+ AsyncTODOQueue.add(ast);
+ break;
+ case JOB_TYPE_EXPORT:
+ case JOB_TYPE_VIEW:
+ case JOB_TYPE_SHARE:
+ startExportingStatusChecker();
+ break;
+ default:
+ break;
+ }
+ } else {
+ Log.w("myApp", "[#] GPSApplication.java - Empty Job, nothing processed");
+ JobProgress = 0;
+ JobsPending = 0;
+ }
+ }
+
+
+ private class AsyncPrepareTracklistContextMenu extends Thread {
+
+ public AsyncPrepareTracklistContextMenu() {
+ }
+
+ public void run() {
+ isContextMenuShareVisible = false;
+ isContextMenuViewVisible = false;
+ ViewInApp = "";
+
+ final PackageManager pm = getPackageManager();
+
+ // ----- menu share
+ Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setType("text/xml");
+ // Verify the intent will resolve to at least one activity
+ if ((intent.resolveActivity(pm) != null)) isContextMenuShareVisible = true;
+
+ // ----- menu view
+ intent = new Intent(Intent.ACTION_VIEW);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setType("application/vnd.google-earth.kml+xml");
+
+ if (prefViewTracksWith == 0) { // KML Viewer
+ intent.setType("application/vnd.google-earth.kml+xml");
+ }
+ if (prefViewTracksWith == 1) { // GPX Viewer
+ intent.setType("application/gpx+xml");
+ }
+ ResolveInfo ri = pm.resolveActivity(intent, 0); // Find default app
+ if (ri != null) {
+ //Log.w("myApp", "[#] GPSApplication.java - Open with: " + ri.activityInfo.applicationInfo.loadLabel(getContext().getPackageManager()));
+ List lri = pm.queryIntentActivities(intent, 0);
+ //Log.w("myApp", "[#] GPSApplication.java - Found " + lri.size() + " viewers:");
+ for (ResolveInfo tmpri : lri) {
+ //Log.w("myApp", "[#] " + ri.activityInfo.applicationInfo.packageName + " - " + tmpri.activityInfo.applicationInfo.packageName);
+ if (ri.activityInfo.applicationInfo.packageName.equals(tmpri.activityInfo.applicationInfo.packageName)) {
+ ViewInApp = ri.activityInfo.applicationInfo.loadLabel(pm).toString();
+ //Log.w("myApp", "[#] DEFAULT --> " + tmpri.activityInfo.applicationInfo.loadLabel(getPackageManager()));
+ } //else Log.w("myApp", "[#] " + tmpri.activityInfo.applicationInfo.loadLabel(getContext().getPackageManager()));
+ }
+ isContextMenuViewVisible = true;
+ }
+ Log.w("myApp", "[#] GPSApplication.java - Tracklist ContextMenu prepared");
+ EventBus.getDefault().post(EventBusMSG.UPDATE_ACTIONBAR);
+ }
+ }
+
+
+ // ------------------------------------------------------------------------- GpsStatus.Listener
+ @Override
+ public void onGpsStatusChanged(final int event) {
+ switch (event) {
+ case GpsStatus.GPS_EVENT_SATELLITE_STATUS:
+ // TODO: get here the status of the GPS, and save into a GpsStatus to be used for satellites visualization;
+ // Use GpsStatus getGpsStatus (GpsStatus status)
+ // https://developer.android.com/reference/android/location/LocationManager.html#getGpsStatus(android.location.GpsStatus)
+ updateSats();
+ break;
+ }
+ }
+
+
+ // --------------------------------------------------------------------------- LocationListener
+ @Override
+ public void onLocationChanged(Location loc) {
+ //if ((loc != null) && (loc.getProvider().equals(LocationManager.GPS_PROVIDER)) {
+ if (loc != null) { // Location data is valid
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { // For API >= 18
+ if ((PrevFix == null) || (loc.isFromMockProvider()!=isMockProvider)) { // Reset the number of satellites when the provider changes between GPS and MOCK
+ isMockProvider = loc.isFromMockProvider();
+ _NumberOfSatellites = NOT_AVAILABLE;
+ _NumberOfSatellitesUsedInFix = NOT_AVAILABLE;
+ if (isMockProvider) Log.w("myApp", "[#] GPSApplication.java - Provider Type = MOCK PROVIDER");
+ else Log.w("myApp", "[#] GPSApplication.java - Provider Type = GPS PROVIDER");
+ }
+ }
+
+ //Log.w("myApp", "[#] GPSApplication.java - onLocationChanged: provider=" + loc.getProvider());
+ if (loc.hasSpeed() && (loc.getSpeed() == 0)) loc.removeBearing(); // Removes bearing if the speed is zero
+ // --------- Workaround for old GPS that are affected to Week Rollover
+ //loc.setTime(loc.getTime() - 619315200000L); // Commented out, it simulate the old GPS hardware Timestamp
+ if (loc.getTime() <= 1388534400000L) // if the Location Time is <= 01/01/2014 00:00:00.000
+ loc.setTime(loc.getTime() + 619315200000L); // Timestamp incremented by 1024×7×24×60×60×1000 = 619315200000 ms
+ // This value must be doubled every 1024 weeks !!!
+ LocationExtended eloc = new LocationExtended(loc);
+ eloc.setNumberOfSatellites(getNumberOfSatellites());
+ eloc.setNumberOfSatellitesUsedInFix(getNumberOfSatellitesUsedInFix());
+ eloc.setSatellite_info(get_satelliteList());
+
+ // eloc.setSatelliteDescription(get_satelliteList());
+ boolean ForceRecord = false;
+
+ gpsunavailablehandler.removeCallbacks(unavailr); // Cancel the previous unavail countdown handler
+ gpsunavailablehandler.postDelayed(unavailr, GPSUNAVAILABLEHANDLERTIMER); // starts the unavailability timeout (in 7 sec.)
+
+ if (GPSStatus != GPS_OK) {
+ if (GPSStatus != GPS_STABILIZING) {
+ GPSStatus = GPS_STABILIZING;
+ _Stabilizer = StabilizingSamples;
+ EventBus.getDefault().post(EventBusMSG.UPDATE_FIX);
+ }
+ else _Stabilizer--;
+ if (_Stabilizer == 0) GPSStatus = GPS_OK;
+ PrevFix = eloc;
+ PrevRecordedFix = eloc;
+ isPrevFixRecorded = true;
+ }
+
+ // Save fix in case this is a STOP or a START (the speed is "old>0 and new=0" or "old=0 and new>0")
+ if ((PrevFix != null) && (PrevFix.getLocation().hasSpeed()) && (eloc.getLocation().hasSpeed()) && (GPSStatus == GPS_OK) && (Recording)
+ && (((eloc.getLocation().getSpeed() == 0) && (PrevFix.getLocation().getSpeed() != 0)) || ((eloc.getLocation().getSpeed() != 0) && (PrevFix.getLocation().getSpeed() == 0)))) {
+ if (!isPrevFixRecorded) { // Record the old sample if not already recorded
+ AsyncTODO ast = new AsyncTODO();
+ ast.TaskType = "TASK_ADDLOCATION";
+ ast.location = PrevFix;
+ AsyncTODOQueue.add(ast);
+ PrevRecordedFix = PrevFix;
+ isPrevFixRecorded = true;
+ }
+
+ ForceRecord = true; // + Force to record the new
+ }
+
+ if (GPSStatus == GPS_OK) {
+ AsyncTODO ast = new AsyncTODO();
+ if ((Recording) && ((prefGPSdistance == 0) || (PrevRecordedFix == null) || (ForceRecord) || (loc.distanceTo(PrevRecordedFix.getLocation()) >= prefGPSdistance))) {
+ PrevRecordedFix = eloc;
+ ast.TaskType = "TASK_ADDLOCATION";
+ ast.location = eloc;
+ AsyncTODOQueue.add(ast);
+ isPrevFixRecorded = true;
+ } else {
+ ast.TaskType = "TASK_UPDATEFIX";
+ ast.location = eloc;
+ AsyncTODOQueue.add(ast);
+ isPrevFixRecorded = false;
+ }
+
+ if (PlacemarkRequest) {
+ _currentPlacemark = new LocationExtended(loc);
+ _currentPlacemark.setNumberOfSatellites(getNumberOfSatellites());
+ _currentPlacemark.setNumberOfSatellitesUsedInFix(getNumberOfSatellitesUsedInFix());
+ _currentPlacemark.setSatellite_info(get_satelliteList());
+ PlacemarkRequest = false;
+ EventBus.getDefault().post(EventBusMSG.UPDATE_TRACK);
+ EventBus.getDefault().post(EventBusMSG.REQUEST_ADD_PLACEMARK);
+
+ }
+ PrevFix = eloc;
+ }
+ }
+ }
+
+ @Override
+ public void onProviderDisabled(String provider) {
+ GPSStatus = GPS_DISABLED;
+ EventBus.getDefault().post(EventBusMSG.UPDATE_FIX);
+ }
+
+ @Override
+ public void onProviderEnabled(String provider) {
+ GPSStatus = GPS_SEARCHING;
+ EventBus.getDefault().post(EventBusMSG.UPDATE_FIX);
+ }
+
+
+ @Override
+ public void onStatusChanged(String provider, int status, Bundle extras) {
+ // This is called when the GPS status changes
+ switch (status) {
+ case LocationProvider.OUT_OF_SERVICE:
+ //Log.w("myApp", "[#] GPSApplication.java - GPS Out of Service");
+ gpsunavailablehandler.removeCallbacks(unavailr); // Cancel the previous unavail countdown handler
+ GPSStatus = GPS_OUTOFSERVICE;
+ EventBus.getDefault().post(EventBusMSG.UPDATE_FIX);
+ //Toast.makeText( getApplicationContext(), "GPS Out of Service", Toast.LENGTH_SHORT).show();
+ break;
+ case LocationProvider.TEMPORARILY_UNAVAILABLE:
+ //Log.w("myApp", "[#] GPSApplication.java - GPS Temporarily Unavailable");
+ gpsunavailablehandler.removeCallbacks(unavailr); // Cancel the previous unavail countdown handler
+ GPSStatus = GPS_TEMPORARYUNAVAILABLE;
+ EventBus.getDefault().post(EventBusMSG.UPDATE_FIX);
+ //Toast.makeText( getApplicationContext(), "GPS Temporarily Unavailable", Toast.LENGTH_SHORT).show();
+ break;
+ case LocationProvider.AVAILABLE:
+ gpsunavailablehandler.removeCallbacks(unavailr); // Cancel the previous unavail countdown handler
+ //Log.w("myApp", "[#] GPSApplication.java - GPS Available: " + _NumberOfSatellites + " satellites");
+ break;
+ }
+ }
+
+
+ public void UpdateTrackList() {
+ long ID = GPSDataBase.getLastTrackID();
+ List _OldArrayListTracks = new ArrayList();
+ _OldArrayListTracks.addAll(_ArrayListTracks);
+
+ if (ID > 0) {
+ synchronized(_ArrayListTracks) {
+ // Save Selections
+ ArrayList SelectedT = new ArrayList<>();
+ for (Track T : _ArrayListTracks) {
+ if (T.isSelected()) SelectedT.add(T.getId());
+ }
+
+ // Update the List
+ _ArrayListTracks.clear();
+ _ArrayListTracks.addAll(GPSDataBase.getTracksList(0, ID - 1));
+ if ((ID > 1) && (GPSDataBase.getTrack(ID - 1) != null)) {
+ String fname = (ID - 1) + ".png";
+ File file = new File(getApplicationContext().getFilesDir() + "/Thumbnails/", fname);
+ if (!file.exists()) Th = new Thumbnailer(ID - 1);
+ }
+ if (_currentTrack.getNumberOfLocations() + _currentTrack.getNumberOfPlacemarks() > 0) {
+ Log.w("myApp", "[#] GPSApplication.java - Update Tracklist: current track (" + _currentTrack.getId() + ") visible into the tracklist");
+ _ArrayListTracks.add(0, _currentTrack);
+ } else
+ Log.w("myApp", "[#] GPSApplication.java - Update Tracklist: current track not visible into the tracklist");
+
+ // Restore the selection state
+ for (Track T : _ArrayListTracks) {
+ for (Long SelT : SelectedT) {
+ if (SelT == T.getId()) {
+ T.setSelected(true);
+ break;
+ }
+ }
+ }
+ }
+ EventBus.getDefault().post(EventBusMSG.UPDATE_TRACKLIST);
+ //Log.w("myApp", "[#] GPSApplication.java - Update Tracklist: Added " + _ArrayListTracks.size() + " tracks");
+ }
+ }
+
+
+// PREFERENCES LOADER ------------------------------------------------------------------------------
+
+ private void LoadPreferences() {
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
+
+ // ---------Conversion from the previous versions of GPS Logger preferences
+ if (preferences.contains("prefShowImperialUnits")) { // The old boolean setting for imperial units in v.1.1.5
+ Log.w("myApp", "[#] GPSApplication.java - Old setting prefShowImperialUnits present. Converting to new preference PrefUM.");
+ boolean imperialUM = preferences.getBoolean("prefShowImperialUnits", false);
+ SharedPreferences.Editor editor = preferences.edit();
+ editor.putString("prefUM", (imperialUM ? "8" : "0"));
+ editor.remove("prefShowImperialUnits");
+ editor.commit();
+ }
+
+ // ---------Remove the prefIsStoragePermissionChecked in preferences if present
+
+ if (preferences.contains("prefIsStoragePermissionChecked")) {
+ SharedPreferences.Editor editor = preferences.edit();
+ editor.remove("prefIsStoragePermissionChecked");
+ editor.commit();
+ }
+
+ // -----------------------------------------------------------------------
+
+ //prefKeepScreenOn = preferences.getBoolean("prefKeepScreenOn", true);
+ prefGPSWeekRolloverCorrected = preferences.getBoolean("prefGPSWeekRolloverCorrected", false);
+ prefShowDecimalCoordinates = preferences.getBoolean("prefShowDecimalCoordinates", false);
+ prefViewTracksWith = Integer.valueOf(preferences.getString("prefViewTracksWith", "0"));
+ prefUM = Integer.valueOf(preferences.getString("prefUM", "0")) + Integer.valueOf(preferences.getString("prefUMSpeed", "1"));
+ prefGPSdistance = Float.valueOf(preferences.getString("prefGPSdistance", "0"));
+ prefEGM96AltitudeCorrection = preferences.getBoolean("prefEGM96AltitudeCorrection", false);
+ prefAltitudeCorrection = Double.valueOf(preferences.getString("prefAltitudeCorrection", "0"));
+ Log.w("myApp", "[#] GPSApplication.java - Manual Correction set to " + prefAltitudeCorrection + " m");
+ prefExportKML = preferences.getBoolean("prefExportKML", true);
+ prefExportGPX = preferences.getBoolean("prefExportGPX", true);
+ prefExportTXT = preferences.getBoolean("prefExportTXT", false);
+ prefKMLAltitudeMode = Integer.valueOf(preferences.getString("prefKMLAltitudeMode", "1"));
+ prefGPXVersion = Integer.valueOf(preferences.getString("prefGPXVersion", "100")); // Default value = v.1.0
+ prefShowTrackStatsType = Integer.valueOf(preferences.getString("prefShowTrackStatsType", "0"));
+ prefShowDirections = Integer.valueOf(preferences.getString("prefShowDirections", "0"));
+
+ long oldGPSupdatefrequency = prefGPSupdatefrequency;
+ prefGPSupdatefrequency = Long.valueOf(preferences.getString("prefGPSupdatefrequency", "1000"));
+
+ // ---------------------------------------------- Update the GPS Update Frequency if needed
+ if (oldGPSupdatefrequency != prefGPSupdatefrequency) updateGPSLocationFrequency();
+
+ // ---------------------------------------------------------------- If no Exportation formats are enabled, enable the GPX one
+ if (!prefExportKML && !prefExportGPX && !prefExportTXT) {
+ SharedPreferences.Editor editor = preferences.edit();
+ editor.putBoolean("prefExportGPX", true);
+ editor.commit();
+ prefExportGPX = true;
+ }
+
+ // ---------------------------------------------------------------- Load EGM Grid if needed
+ EGM96 egm96 = EGM96.getInstance();
+ if (egm96 != null) {
+ if (!egm96.isEGMGridLoaded()) {
+ egm96.LoadGridFromFile(Environment.getExternalStorageDirectory() + "/GPSLogger/AppData/WW15MGH.DAC", getApplicationContext().getFilesDir() + "/WW15MGH.DAC");
+ }
+ }
+
+ // ------------------------------------------------------------------- Request of UI Update
+ EventBus.getDefault().post(EventBusMSG.APPLY_SETTINGS);
+ EventBus.getDefault().post(EventBusMSG.UPDATE_FIX);
+ EventBus.getDefault().post(EventBusMSG.UPDATE_TRACK);
+ EventBus.getDefault().post(EventBusMSG.UPDATE_TRACKLIST);
+ }
+
+
+// THE THREAD THAT DOES ASYNCHRONOUS OPERATIONS ---------------------------------------------------
+
+
+ class AsyncTODO {
+ String TaskType;
+ LocationExtended location;
+ }
+
+ private BlockingQueue AsyncTODOQueue = new LinkedBlockingQueue<>();
+
+ private class AsyncUpdateThreadClass extends Thread {
+
+ Track track;
+ LocationExtended locationExtended;
+
+ public AsyncUpdateThreadClass() {}
+
+ public void run() {
+
+ track = _currentTrack;
+ EventBus.getDefault().post(EventBusMSG.UPDATE_TRACK);
+ UpdateTrackList();
+
+ // ----------------------------------------------------------------------------------------
+ // Apply the GPS Week Rollover Correction, for data already stored into the DB
+ // ----------------------------------------------------------------------------------------
+
+ if (!prefGPSWeekRolloverCorrected) {
+ if (!isFirstRun) {
+ Log.w("myApp", "[#] GPSApplication.java - CORRECTING DATA FOR GPS WEEK ROLLOVER");
+ GPSDataBase.CorrectGPSWeekRollover();
+ Log.w("myApp", "[#] GPSApplication.java - DATA FOR GPS WEEK ROLLOVER CORRECTED");
+ UpdateTrackList();
+ Log.w("myApp", "[#] GPSApplication.java - TRACKLIST UPDATED WITH THE CORRECTED NAMES");
+ }
+ prefGPSWeekRolloverCorrected = true;
+ SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).edit();
+ editor.putBoolean("prefGPSWeekRolloverCorrected", true);
+ editor.commit();
+ }
+ // ----------------------------------------------------------------------------------------
+
+
+ while (true) {
+ AsyncTODO asyncTODO;
+ try {
+ asyncTODO = AsyncTODOQueue.take();
+ } catch (InterruptedException e) {
+ Log.w("myApp", "[!] Buffer not available: " + e.getMessage());
+ break;
+ }
+
+ // Task: Create new track (if needed)
+ if (asyncTODO.TaskType.equals("TASK_NEWTRACK")) {
+ if ((track.getNumberOfLocations() != 0) || (track.getNumberOfPlacemarks() != 0)) {
+ // ---- Delete 2 thumbs files forward - in case of user deleted DB in App manager (pngs could be already presents for the new IDS)
+ String fname = (track.getId() + 1) +".png";
+ File file = new File(getApplicationContext().getFilesDir() + "/Thumbnails/", fname);
+ if (file.exists ()) file.delete ();
+ fname = (track.getId() + 2) +".png";
+ file = new File(getApplicationContext().getFilesDir() + "/Thumbnails/", fname);
+ if (file.exists ()) file.delete ();
+ track = new Track();
+ // ----
+ track.setId(GPSDataBase.addTrack(track));
+ Log.w("myApp", "[#] GPSApplication.java - TASK_NEWTRACK: " + track.getId());
+ _currentTrack = track;
+ UpdateTrackList();
+ } else Log.w("myApp", "[#] GPSApplication.java - TASK_NEWTRACK: Track " + track.getId() + " already empty (New track not created)");
+ _currentTrack = track;
+ EventBus.getDefault().post(EventBusMSG.UPDATE_TRACK);
+ }
+
+ // Task: Add location to current track
+ if (asyncTODO.TaskType.equals("TASK_ADDLOCATION")) {
+ locationExtended = new LocationExtended(asyncTODO.location.getLocation());
+ locationExtended.setNumberOfSatellites(asyncTODO.location.getNumberOfSatellites());
+ locationExtended.setNumberOfSatellitesUsedInFix(asyncTODO.location.getNumberOfSatellitesUsedInFix());
+ locationExtended.setSatellite_info(asyncTODO.location.getSatellite_info());
+ _currentLocationExtended = locationExtended;
+ EventBus.getDefault().post(EventBusMSG.UPDATE_FIX);
+ track.add(locationExtended);
+ GPSDataBase.addLocationToTrack(locationExtended, track);
+ //Log.w("myApp", "[#] GPSApplication.java - TASK_ADDLOCATION: Added new Location in " + track.getId());
+ _currentTrack = track;
+ EventBus.getDefault().post(EventBusMSG.UPDATE_TRACK);
+ if (_currentTrack.getNumberOfLocations() + _currentTrack.getNumberOfPlacemarks() == 1) UpdateTrackList();
+ }
+
+ // Task: Add a placemark to current track
+ if (asyncTODO.TaskType.equals("TASK_ADDPLACEMARK")) {
+ locationExtended = new LocationExtended(asyncTODO.location.getLocation());
+ locationExtended.setDescription(asyncTODO.location.getDescription());
+ locationExtended.setNumberOfSatellites(asyncTODO.location.getNumberOfSatellites());
+ locationExtended.setNumberOfSatellitesUsedInFix(asyncTODO.location.getNumberOfSatellitesUsedInFix());
+ locationExtended.setSatellite_info(asyncTODO.location.getSatellite_info());
+ // locationExtended.setSatelliteinfo(asyncTODO.location.get_satellit)
+ track.addPlacemark(locationExtended);
+ GPSDataBase.addPlacemarkToTrack(locationExtended, track);
+ _currentTrack = track;
+ EventBus.getDefault().post(EventBusMSG.UPDATE_TRACK);
+ if (_currentTrack.getNumberOfLocations() + _currentTrack.getNumberOfPlacemarks() == 1) UpdateTrackList();
+ }
+
+ // Task: Update current Fix
+ if (asyncTODO.TaskType.equals("TASK_UPDATEFIX")) {
+ _currentLocationExtended = new LocationExtended(asyncTODO.location.getLocation());
+ _currentLocationExtended.setNumberOfSatellites(asyncTODO.location.getNumberOfSatellites());
+ _currentLocationExtended.setNumberOfSatellitesUsedInFix(asyncTODO.location.getNumberOfSatellitesUsedInFix());
+ _currentLocationExtended.setSatellite_info(asyncTODO.location.getSatellite_info());
+ EventBus.getDefault().post(EventBusMSG.UPDATE_FIX);
+ }
+
+ // Task: Delete some tracks
+ if (asyncTODO.TaskType.startsWith("TASK_DELETE_TRACKS")) {
+
+ String STokens = asyncTODO.TaskType.substring(19);
+ List tokens = new ArrayList<>();
+ StringTokenizer tokenizer = new StringTokenizer(STokens, " ");
+ while (tokenizer.hasMoreElements()) {
+ tokens.add(tokenizer.nextToken());
+ }
+ if (!tokens.isEmpty()) {
+ JobProgress = 0;
+ int TracksToBeDeleted = tokens.size();
+ int TracksDeleted = 0;
+ for (String s : tokens) {
+ Track track = null; // The track found in the _ArrayListTracks
+ int i = Integer.valueOf(s);
+ if (i != _currentTrack.getId()) { // Prevent the deletion of the current track
+ synchronized (_ArrayListTracks) {
+ for (Track T : _ArrayListTracks) {
+ if (T.getId() == i) {
+ track = T;
+ GPSDataBase.DeleteTrack(i);
+ Log.w("myApp", "[#] GPSApplication.java - TASK_DELETE_TRACKS: Track " + i + " deleted.");
+ _ArrayListTracks.remove(T);
+ break;
+ }
+ }
+ }
+ if (track != null) {
+ // Delete track files
+ DeleteFile(Environment.getExternalStorageDirectory() + "/GPSLogger/AppData/" + track.getName() + ".txt");
+ DeleteFile(Environment.getExternalStorageDirectory() + "/GPSLogger/AppData/" + track.getName() + ".kml");
+ DeleteFile(Environment.getExternalStorageDirectory() + "/GPSLogger/AppData/" + track.getName() + ".gpx");
+ DeleteFile(getApplicationContext().getFilesDir() + "/Thumbnails/" + track.getId() + ".png");
+ if (DeleteAlsoExportedFiles) {
+ // Delete exported files
+ DeleteFile(Environment.getExternalStorageDirectory() + "/GPSLogger/" + track.getName() + ".txt");
+ DeleteFile(Environment.getExternalStorageDirectory() + "/GPSLogger/" + track.getName() + ".kml");
+ DeleteFile(Environment.getExternalStorageDirectory() + "/GPSLogger/" + track.getName() + ".gpx");
+ }
+
+ TracksDeleted++;
+ JobProgress = (int) Math.round(1000L * TracksDeleted / TracksToBeDeleted);
+ EventBus.getDefault().post(EventBusMSG.UPDATE_JOB_PROGRESS);
+ if (JobsPending > 0) JobsPending--;
+ }
+ } else {
+ Log.w("myApp", "[#] GPSApplication.java - TASK_DELETE_TRACKS: Unable to delete the current track!");
+ TracksDeleted++;
+ JobProgress = (int) Math.round(1000L * TracksDeleted / TracksToBeDeleted);
+ EventBus.getDefault().post(EventBusMSG.UPDATE_JOB_PROGRESS);
+ if (JobsPending > 0) JobsPending--;
+ }
+ }
+ }
+ JobProgress = 0;
+ EventBus.getDefault().post(EventBusMSG.UPDATE_JOB_PROGRESS);
+ EventBus.getDefault().post(EventBusMSG.NOTIFY_TRACKS_DELETED);
+ }
+ }
+ }
+ }
+
+
+
+
+
+// THE THREAD THAT GENERATES A TRACK THUMBNAIL -----------------------------------------------------
+
+ public class Thumbnailer {
+
+ long Id;
+ long NumberOfLocations;
+
+ private Paint drawPaint = new Paint();
+ private Paint BGPaint = new Paint();
+ private Paint EndDotdrawPaint = new Paint();
+ private Paint EndDotBGPaint = new Paint();
+ private int Size = (int)(getResources().getDimension(R.dimen.thumbSize));
+
+ private int Margin = (int) Math.ceil(getResources().getDimension(R.dimen.thumbLineWidth) * 3);
+ private int Size_Minus_Margins = Size - 2 * Margin;
+
+ private double MinLatitude;
+ private double MinLongitude;
+
+ double Distance_Proportion;
+ double DrawScale;
+ double Lat_Offset;
+ double Lon_Offset;
+
+ private AsyncThumbnailThreadClass asyncThumbnailThreadClass = new AsyncThumbnailThreadClass();
+
+ public Thumbnailer(long ID) {
+
+ Track track = GPSDataBase.getTrack(ID);
+ //Log.w("myApp", "[#] GPSApplication.java - Bitmap Size = " + Size);
+
+ if ((track.getNumberOfLocations() > 2) && (track.getDistance() >= 15) && (track.getValidMap() != 0)) {
+ Id = track.getId();
+ NumberOfLocations = track.getNumberOfLocations();
+
+ // Setup Paints
+ //drawPaint.setColor(getResources().getColor(R.color.colorThumbnailLineColor));
+ drawPaint.setAntiAlias(true);
+ drawPaint.setStrokeWidth(getResources().getDimension(R.dimen.thumbLineWidth));
+ //drawPaint.setStrokeWidth(2);
+ drawPaint.setStyle(Paint.Style.STROKE);
+ drawPaint.setStrokeJoin(Paint.Join.ROUND);
+ drawPaint.setStrokeCap(Paint.Cap.ROUND);
+
+ BGPaint.setColor(Color.BLACK);
+ BGPaint.setAntiAlias(true);
+ BGPaint.setStrokeWidth(getResources().getDimension(R.dimen.thumbLineWidth) * 3);
+ //BGPaint.setStrokeWidth(6);
+ BGPaint.setStyle(Paint.Style.STROKE);
+ BGPaint.setStrokeJoin(Paint.Join.ROUND);
+ BGPaint.setStrokeCap(Paint.Cap.ROUND);
+
+ // EndDotdrawPaint.setColor(getResources().getColor(R.color.colorThumbnailLineColor));
+ EndDotdrawPaint.setAntiAlias(true);
+ EndDotdrawPaint.setStrokeWidth(getResources().getDimension(R.dimen.thumbLineWidth) * 2.5f);
+ EndDotdrawPaint.setStyle(Paint.Style.STROKE);
+ EndDotdrawPaint.setStrokeJoin(Paint.Join.ROUND);
+ EndDotdrawPaint.setStrokeCap(Paint.Cap.ROUND);
+
+ EndDotBGPaint.setColor(Color.BLACK);
+ EndDotBGPaint.setAntiAlias(true);
+ EndDotBGPaint.setStrokeWidth(getResources().getDimension(R.dimen.thumbLineWidth) * 4.5f);
+ EndDotBGPaint.setStyle(Paint.Style.STROKE);
+ EndDotBGPaint.setStrokeJoin(Paint.Join.ROUND);
+ EndDotBGPaint.setStrokeCap(Paint.Cap.ROUND);
+
+ // Calculate the drawing scale
+ double Mid_Latitude = (track.getMax_Latitude() + track.getMin_Latitude()) / 2;
+ double Angle_From_Equator = Math.abs(Mid_Latitude);
+
+ Distance_Proportion = Math.cos(Math.toRadians(Angle_From_Equator));
+ //Log.w("myApp", "[#] GPSApplication.java - Distance_Proportion = " + Distance_Proportion);
+
+ DrawScale = Math.max(track.getMax_Latitude() - track.getMin_Latitude(), Distance_Proportion * (track.getMax_Longitude() - track.getMin_Longitude()));
+ Lat_Offset = Size_Minus_Margins * (1 - (track.getMax_Latitude() - track.getMin_Latitude()) / DrawScale) / 2;
+ Lon_Offset = Size_Minus_Margins * (1 - (Distance_Proportion * (track.getMax_Longitude() - track.getMin_Longitude()) / DrawScale)) / 2;
+
+ MinLatitude = track.getMin_Latitude();
+ MinLongitude = track.getMin_Longitude();
+
+ asyncThumbnailThreadClass.start();
+ }
+ }
+
+ private class AsyncThumbnailThreadClass extends Thread {
+
+ public AsyncThumbnailThreadClass() {}
+
+ public void run() {
+ Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
+
+ String fname = Id + ".png";
+ File file = new File(getApplicationContext().getFilesDir() + "/Thumbnails/", fname);
+ if (file.exists()) file.delete();
+
+ if (DrawScale > 0) {
+ int GroupOfLocations = 200;
+ Path path = new Path();
+ List latlngList = new ArrayList<>();
+
+ //Log.w("myApp", "[#] GPSApplication.java - Thumbnailer Thread started");
+ for (int i = 0; i < NumberOfLocations; i += GroupOfLocations) {
+ latlngList.addAll(GPSDataBase.getLatLngList(Id, i, i + GroupOfLocations - 1));
+ }
+ //Log.w("myApp", "[#] GPSApplication.java - Added " + latlngList.size() + " items to Path");
+ if (!latlngList.isEmpty()) {
+ Bitmap ThumbBitmap = Bitmap.createBitmap(Size, Size, Bitmap.Config.ARGB_8888);
+ Canvas ThumbCanvas = new Canvas(ThumbBitmap);
+
+ for (int i = 0; i < latlngList.size(); i++) {
+ if (i == 0)
+ path.moveTo((float) (Lon_Offset + Margin + Size_Minus_Margins * ((latlngList.get(i).Longitude - MinLongitude) * Distance_Proportion / DrawScale)),
+ (float) (-Lat_Offset + Size - (Margin + Size_Minus_Margins * ((latlngList.get(i).Latitude - MinLatitude) / DrawScale))));
+ else
+ path.lineTo((float) (Lon_Offset + Margin + Size_Minus_Margins * ((latlngList.get(i).Longitude - MinLongitude) * Distance_Proportion / DrawScale)),
+ (float) (-Lat_Offset + Size - (Margin + Size_Minus_Margins * ((latlngList.get(i).Latitude - MinLatitude) / DrawScale))));
+ }
+ ThumbCanvas.drawPath(path, BGPaint);
+ ThumbCanvas.drawPoint((float) (Lon_Offset + Margin + Size_Minus_Margins * ((latlngList.get(latlngList.size()-1).Longitude - MinLongitude) * Distance_Proportion / DrawScale)),
+ (float) (-Lat_Offset + Size - (Margin + Size_Minus_Margins * ((latlngList.get(latlngList.size()-1).Latitude - MinLatitude) / DrawScale))), EndDotBGPaint);
+ ThumbCanvas.drawPath(path, drawPaint);
+ ThumbCanvas.drawPoint((float) (Lon_Offset + Margin + Size_Minus_Margins * ((latlngList.get(latlngList.size()-1).Longitude - MinLongitude) * Distance_Proportion / DrawScale)),
+ (float) (-Lat_Offset + Size - (Margin + Size_Minus_Margins * ((latlngList.get(latlngList.size()-1).Latitude - MinLatitude) / DrawScale))), EndDotdrawPaint);
+
+ try {
+ FileOutputStream out = new FileOutputStream(file);
+ //Log.w("myApp", "[#] GPSApplication.java - FileOutputStream out = new FileOutputStream(file)");
+ //boolean res = ThumbBitmap.compress(Bitmap.CompressFormat.PNG, 60, out);
+ ThumbBitmap.compress(Bitmap.CompressFormat.PNG, 60, out);
+ //Log.w("myApp", "[#] GPSApplication.java - ThumbBitmap.compress(Bitmap.CompressFormat.PNG, 60, out): " + res);
+ out.flush();
+ //Log.w("myApp", "[#] GPSApplication.java - out.flush();");
+ out.close();
+ //Log.w("myApp", "[#] GPSApplication.java - out.close();");
+ } catch (Exception e) {
+ e.printStackTrace();
+ //Log.w("myApp", "[#] GPSApplication.java - Unable to save: " + Environment.getExternalStorageDirectory() + "/GPSLogger/AppData/" + fname);
+ }
+
+ EventBus.getDefault().post(EventBusMSG.REFRESH_TRACKLIST);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/Satellite/GPSService.java b/app/src/main/java/Satellite/GPSService.java
new file mode 100644
index 0000000..2cd2787
--- /dev/null
+++ b/app/src/main/java/Satellite/GPSService.java
@@ -0,0 +1,121 @@
+
+package Satellite;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.PowerManager;
+import android.support.v4.app.NotificationCompat;
+import android.util.Log;
+
+public class GPSService extends Service {
+ // Singleton instance
+ private static GPSService singleton;
+ public static GPSService getInstance(){
+ return singleton;
+ }
+ // IBinder
+ private final IBinder mBinder = new LocalBinder();
+ public class LocalBinder extends Binder { //returns the instance of the service
+ public GPSService getServiceInstance(){
+ return GPSService.this;
+ }
+ }
+
+ // PARTIAL_WAKELOCK
+ private PowerManager powerManager;
+ PowerManager.WakeLock wakeLock;
+
+ private Notification getNotification() {
+ final String CHANNEL_ID = "GPSLoggerServiceChannel";
+
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID);
+ //builder.setSmallIcon(R.drawable.ic_notification_24dp)
+ /* builder.setSmallIcon(R.mipmap.ic_notify_24dp)
+ .setColor(getResources().getColor(R.color.colorPrimaryLight))
+ .setContentTitle(getString(R.string.app_name))
+ .setShowWhen(false)
+ .setPriority(NotificationCompat.PRIORITY_LOW)
+ .setCategory(NotificationCompat.CATEGORY_SERVICE)
+ .setOngoing(true)
+ .setContentText(getString(R.string.notification_contenttext));
+*/
+ //if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
+ // builder.setPriority(NotificationCompat.PRIORITY_LOW);
+ //}
+
+ final Intent startIntent = new Intent(getApplicationContext(), GPSActivity.class);
+ startIntent.setAction(Intent.ACTION_MAIN);
+ startIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+ //startIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+ PendingIntent contentIntent = PendingIntent.getActivity(getApplicationContext(), 1, startIntent, 0);
+ builder.setContentIntent(contentIntent);
+ return builder.build();
+ }
+
+ /* THREAD FOR DEBUG PURPOSE
+ Thread t = new Thread() {
+ public void run() {
+ boolean i = true;
+ while (i) {
+ try {
+ sleep(1000);
+ Log.w("myApp", "[#] GPSService.java - ** RUNNING **");
+ } catch (InterruptedException e) {
+ i = false;
+ }
+ }
+ }
+ }; */
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ singleton = this;
+ // THREAD FOR DEBUG PURPOSE
+ //if (!t.isAlive()) {
+ // t.start();
+ //}
+
+ // PARTIAL_WAKELOCK
+ powerManager = (PowerManager) getSystemService(POWER_SERVICE);
+ wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"GPSLogger:wakelock");
+ Log.w("myApp", "[#] GPSService.java - CREATE = onCreate");
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ startForeground(1, getNotification());
+ Log.w("myApp", "[#] GPSService.java - START = onStartCommand");
+ return START_NOT_STICKY;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (wakeLock != null && !wakeLock.isHeld()) {
+ wakeLock.acquire();
+ Log.w("myApp", "[#] GPSService.java - WAKELOCK acquired");
+ }
+ Log.w("myApp", "[#] GPSService.java - BIND = onBind");
+ return mBinder;
+ //return null;
+ }
+
+ @Override
+ public void onDestroy() {
+ // PARTIAL_WAKELOCK
+ if (wakeLock != null && wakeLock.isHeld()) {
+ wakeLock.release();
+ Log.w("myApp", "[#] GPSService.java - WAKELOCK released");
+ }
+
+ Log.w("myApp", "[#] GPSService.java - DESTROY = onDestroy");
+ // THREAD FOR DEBUG PURPOSE
+ //if (t.isAlive()) t.interrupt();
+ super.onDestroy();
+ }
+}
diff --git a/app/src/main/java/Satellite/LatLng.java b/app/src/main/java/Satellite/LatLng.java
new file mode 100644
index 0000000..63de7fc
--- /dev/null
+++ b/app/src/main/java/Satellite/LatLng.java
@@ -0,0 +1,7 @@
+
+package Satellite;
+
+class LatLng {
+ double Latitude;
+ double Longitude;
+}
diff --git a/app/src/main/java/Satellite/LocationExtended.java b/app/src/main/java/Satellite/LocationExtended.java
new file mode 100644
index 0000000..c8686c0
--- /dev/null
+++ b/app/src/main/java/Satellite/LocationExtended.java
@@ -0,0 +1,97 @@
+
+package Satellite;
+
+import android.location.Location;
+
+public class LocationExtended {
+
+ private final int NOT_AVAILABLE = -100000;
+
+ private Location _Location;
+ private String _Description = "";
+ private double _AltitudeEGM96Correction = NOT_AVAILABLE;
+ private int _NumberOfSatellites = NOT_AVAILABLE;
+ private int _NumberOfSatellitesUsedInFix = NOT_AVAILABLE;
+ private String Satellite_info = "";
+
+
+ // Constructor
+ public LocationExtended(Location location) {
+ _Location = location;
+ EGM96 egm96 = EGM96.getInstance();
+ if (egm96 != null) {
+ if (egm96.isEGMGridLoaded()) _AltitudeEGM96Correction = egm96.getEGMCorrection(_Location.getLatitude(), _Location.getLongitude());
+ }
+ }
+
+ // Getters and Setters -------------------------------------------------------------------------
+
+ public Location getLocation() {
+ return _Location;
+ }
+
+ public double getLatitude() { return _Location.getLatitude(); }
+ public double getLongitude() { return _Location.getLongitude(); }
+ public double getAltitude() { return _Location.hasAltitude() ? _Location.getAltitude() : NOT_AVAILABLE; }
+ public float getSpeed() { return _Location.hasSpeed() ? _Location.getSpeed() : NOT_AVAILABLE; }
+ public float getAccuracy() { return _Location.hasAccuracy() ? _Location.getAccuracy() : NOT_AVAILABLE; }
+ public float getBearing() { return _Location.hasBearing() ? _Location.getBearing() : NOT_AVAILABLE; }
+ public long getTime() { return _Location.getTime(); }
+ public String getSatellite_info(){return Satellite_info; }
+
+ public String getDescription() {
+ return _Description;
+ }
+
+ public void setDescription(String Description) {
+ this._Description = Description;
+ }
+
+ public void setNumberOfSatellites(int numberOfSatellites) {
+ _NumberOfSatellites = numberOfSatellites;
+ }
+
+ public int getNumberOfSatellites() {
+ return _NumberOfSatellites;
+ }
+
+ public void setNumberOfSatellitesUsedInFix(int numberOfSatellites) {
+ _NumberOfSatellitesUsedInFix = numberOfSatellites;
+ }
+
+ public void setSatellite_info(String satellite_info){
+ this.Satellite_info = satellite_info;
+
+
+ }
+
+ public int getNumberOfSatellitesUsedInFix() {
+ return _NumberOfSatellitesUsedInFix;
+ }
+
+
+
+ public double getAltitudeEGM96Correction(){
+ if (_AltitudeEGM96Correction == NOT_AVAILABLE) {
+ //Log.w("myApp", "[#] LocationExtended.java - _AltitudeEGM96Correction == NOT_AVAILABLE");
+ EGM96 egm96 = EGM96.getInstance();
+ if (egm96 != null) {
+ if (egm96.isEGMGridLoaded()) _AltitudeEGM96Correction = egm96.getEGMCorrection(_Location.getLatitude(), _Location.getLongitude());
+ }
+ }
+ return _AltitudeEGM96Correction;
+ }
+
+ public double getAltitudeCorrected(double AltitudeManualCorrection, boolean EGMCorrection) {
+ if (_Location != null) {
+ if (!_Location.hasAltitude()) return NOT_AVAILABLE;
+ if ((EGMCorrection) && (getAltitudeEGM96Correction() != NOT_AVAILABLE)) return _Location.getAltitude() - getAltitudeEGM96Correction() + AltitudeManualCorrection;
+ else return _Location.getAltitude() + AltitudeManualCorrection;
+ }
+ return NOT_AVAILABLE;
+ }
+
+
+
+}
+
diff --git a/app/src/main/java/Satellite/PhysicalData.java b/app/src/main/java/Satellite/PhysicalData.java
new file mode 100644
index 0000000..74e18f1
--- /dev/null
+++ b/app/src/main/java/Satellite/PhysicalData.java
@@ -0,0 +1,7 @@
+
+package Satellite;
+
+class PhysicalData {
+ String Value; //The string of the Numerical value of the Physical Quantity
+ String UM; //The string of the Unit of Measurement
+}
diff --git a/app/src/main/java/Satellite/PhysicalDataFormatter.java b/app/src/main/java/Satellite/PhysicalDataFormatter.java
new file mode 100644
index 0000000..64b0c6d
--- /dev/null
+++ b/app/src/main/java/Satellite/PhysicalDataFormatter.java
@@ -0,0 +1,258 @@
+
+package Satellite;
+
+import android.location.Location;
+
+import java.text.SimpleDateFormat;
+import java.util.TimeZone;
+
+import fr.geolabs.dev.mapmint4me.R;
+
+class PhysicalDataFormatter {
+
+ private final int NOT_AVAILABLE = -100000;
+
+ private final int UM_METRIC_MS = 0;
+ private final int UM_METRIC_KMH = 1;
+ private final int UM_IMPERIAL_FPS = 8;
+ private final int UM_IMPERIAL_MPH = 9;
+ private final int UM_NAUTICAL_KN = 16;
+ private final int UM_NAUTICAL_MPH = 17;
+
+ static final byte FORMAT_LATITUDE = 1;
+ static final byte FORMAT_LONGITUDE = 2;
+ static final byte FORMAT_ALTITUDE = 3;
+ static final byte FORMAT_SPEED = 4;
+ static final byte FORMAT_ACCURACY = 5;
+ static final byte FORMAT_BEARING = 6;
+ static final byte FORMAT_DURATION = 7;
+ static final byte FORMAT_SPEED_AVG = 8;
+ static final byte FORMAT_DISTANCE = 9;
+ static final byte FORMAT_TIME = 10;
+
+ private final float M_TO_FT = 3.280839895f;
+ private final float M_TO_NM = 0.000539957f;
+ private final float MS_TO_MPH = 2.2369363f;
+ private final float MS_TO_KMH = 3.6f;
+ private final float MS_TO_KN = 1.943844491f;
+ private final float KM_TO_MI = 0.621371192237f;
+
+ //private PhysicalData _PhysicalData = new PhysicalData();
+ private GPSApplication gpsApplication = GPSApplication.getInstance();
+
+
+ public PhysicalData format(float Number, byte Format) {
+ PhysicalData _PhysicalData = new PhysicalData();
+ _PhysicalData.Value = "";
+ _PhysicalData.UM = "";
+
+ if (Number == NOT_AVAILABLE) return(_PhysicalData); // Returns empty fields if the data is not available
+
+ switch (Format) {
+ case FORMAT_SPEED: // Speed
+ switch (gpsApplication.getPrefUM()) {
+ case UM_METRIC_KMH:
+ _PhysicalData.Value = String.valueOf(Math.round(Number * MS_TO_KMH));
+ _PhysicalData.UM = gpsApplication.getString(R.string.UM_km_h);
+ return(_PhysicalData);
+ case UM_METRIC_MS:
+ _PhysicalData.Value = String.valueOf(Math.round(Number));
+ _PhysicalData.UM = gpsApplication.getString(R.string.UM_m_s);
+ return(_PhysicalData);
+ case UM_IMPERIAL_MPH:
+ case UM_NAUTICAL_MPH:
+ _PhysicalData.Value = String.valueOf(Math.round(Number * MS_TO_MPH));
+ _PhysicalData.UM = gpsApplication.getString(R.string.UM_mph);
+ return(_PhysicalData);
+ case UM_IMPERIAL_FPS:
+ _PhysicalData.Value = String.valueOf(Math.round(Number * M_TO_FT));
+ _PhysicalData.UM = gpsApplication.getString(R.string.UM_fps);
+ return(_PhysicalData);
+ case UM_NAUTICAL_KN:
+ _PhysicalData.Value = String.valueOf(Math.round(Number * MS_TO_KN));
+ _PhysicalData.UM = gpsApplication.getString(R.string.UM_kn);
+ return(_PhysicalData);
+ }
+
+ case FORMAT_SPEED_AVG: // Average Speed, formatted with 1 decimal
+ switch (gpsApplication.getPrefUM()) {
+ case UM_METRIC_KMH:
+ _PhysicalData.Value = String.format("%.1f", (Number * MS_TO_KMH));
+ _PhysicalData.UM = gpsApplication.getString(R.string.UM_km_h);
+ return(_PhysicalData);
+ case UM_METRIC_MS:
+ _PhysicalData.Value = String.format("%.1f", (Number));
+ _PhysicalData.UM = gpsApplication.getString(R.string.UM_m_s);
+ return(_PhysicalData);
+ case UM_IMPERIAL_MPH:
+ case UM_NAUTICAL_MPH:
+ _PhysicalData.Value = String.format("%.1f", (Number * MS_TO_MPH));
+ _PhysicalData.UM = gpsApplication.getString(R.string.UM_mph);
+ return(_PhysicalData);
+ case UM_IMPERIAL_FPS:
+ _PhysicalData.Value = String.format("%.1f", (Number * M_TO_FT));
+ _PhysicalData.UM = gpsApplication.getString(R.string.UM_fps);
+ return(_PhysicalData);
+ case UM_NAUTICAL_KN:
+ _PhysicalData.Value = String.format("%.1f", (Number * MS_TO_KN));
+ _PhysicalData.UM = gpsApplication.getString(R.string.UM_kn);
+ return(_PhysicalData);
+ }
+
+ case FORMAT_ACCURACY: // Accuracy
+ switch (gpsApplication.getPrefUM()) {
+ case UM_METRIC_KMH:
+ case UM_METRIC_MS:
+ _PhysicalData.Value = String.valueOf(Math.round(Number));
+ _PhysicalData.UM = gpsApplication.getString(R.string.UM_m);
+ return(_PhysicalData);
+ case UM_IMPERIAL_MPH:
+ case UM_IMPERIAL_FPS:
+ case UM_NAUTICAL_MPH:
+ case UM_NAUTICAL_KN:
+ _PhysicalData.Value = String.valueOf(Math.round(Number * M_TO_FT));
+ _PhysicalData.UM = gpsApplication.getString(R.string.UM_ft);
+ return(_PhysicalData);
+ }
+
+ case FORMAT_BEARING: // Bearing (Direction)
+ switch (gpsApplication.getPrefShowDirections()) {
+ case 0: // NSWE
+ int dr = (int) Math.round(Number / 22.5);
+ switch (dr) {
+ case 0: _PhysicalData.Value = gpsApplication.getString(R.string.north); return(_PhysicalData);
+ case 1: _PhysicalData.Value = gpsApplication.getString(R.string.north_northeast); return(_PhysicalData);
+ case 2: _PhysicalData.Value = gpsApplication.getString(R.string.northeast); return(_PhysicalData);
+ case 3: _PhysicalData.Value = gpsApplication.getString(R.string.east_northeast); return(_PhysicalData);
+ case 4: _PhysicalData.Value = gpsApplication.getString(R.string.east); return(_PhysicalData);
+ case 5: _PhysicalData.Value = gpsApplication.getString(R.string.east_southeast); return(_PhysicalData);
+ case 6: _PhysicalData.Value = gpsApplication.getString(R.string.southeast); return(_PhysicalData);
+ case 7: _PhysicalData.Value = gpsApplication.getString(R.string.south_southeast); return(_PhysicalData);
+ case 8: _PhysicalData.Value = gpsApplication.getString(R.string.south); return(_PhysicalData);
+ case 9: _PhysicalData.Value = gpsApplication.getString(R.string.south_southwest); return(_PhysicalData);
+ case 10: _PhysicalData.Value = gpsApplication.getString(R.string.southwest); return(_PhysicalData);
+ case 11: _PhysicalData.Value = gpsApplication.getString(R.string.west_southwest); return(_PhysicalData);
+ case 12: _PhysicalData.Value = gpsApplication.getString(R.string.west); return(_PhysicalData);
+ case 13: _PhysicalData.Value = gpsApplication.getString(R.string.west_northwest); return(_PhysicalData);
+ case 14: _PhysicalData.Value = gpsApplication.getString(R.string.northwest); return(_PhysicalData);
+ case 15: _PhysicalData.Value = gpsApplication.getString(R.string.north_northwest); return(_PhysicalData);
+ case 16: _PhysicalData.Value = gpsApplication.getString(R.string.north); return(_PhysicalData);
+ }
+ case 1: // Angle
+ _PhysicalData.Value = String.valueOf(Math.round(Number));
+ return(_PhysicalData);
+ }
+
+ case FORMAT_DISTANCE: // Distance
+ switch (gpsApplication.getPrefUM()) {
+ case UM_METRIC_KMH:
+ case UM_METRIC_MS:
+ if (Number < 1000) {
+ _PhysicalData.Value = String.format("%.0f", (Math.floor(Number)));
+ _PhysicalData.UM = gpsApplication.getString(R.string.UM_m);
+ }
+ else {
+ if (Number < 10000) _PhysicalData.Value = String.format("%.2f" , ((Math.floor(Number / 10.0)))/100.0);
+ else _PhysicalData.Value = String.format("%.1f" , ((Math.floor(Number / 100.0)))/10.0);
+ _PhysicalData.UM = gpsApplication.getString(R.string.UM_km);
+ }
+ return(_PhysicalData);
+ case UM_IMPERIAL_MPH:
+ case UM_IMPERIAL_FPS:
+ if ((Number * M_TO_FT) < 1000) {
+ _PhysicalData.Value = String.format("%.0f", (Math.floor(Number * M_TO_FT)));
+ _PhysicalData.UM = gpsApplication.getString(R.string.UM_ft);
+ }
+ else {
+ if ((Number * KM_TO_MI) < 10000) _PhysicalData.Value = String.format("%.2f", ((Math.floor((Number * KM_TO_MI) / 10.0)))/100.0);
+ else _PhysicalData.Value = String.format("%.1f", ((Math.floor((Number * KM_TO_MI) / 100.0)))/10.0);
+ _PhysicalData.UM = gpsApplication.getString(R.string.UM_mi);
+ }
+ return(_PhysicalData);
+ case UM_NAUTICAL_KN:
+ case UM_NAUTICAL_MPH:
+ if ((Number * M_TO_NM) < 100) _PhysicalData.Value = String.format("%.2f", ((Math.floor((Number * M_TO_NM) * 100.0))) / 100.0);
+ else _PhysicalData.Value = String.format("%.1f", ((Math.floor((Number * M_TO_NM) * 10.0))) / 10.0);
+ _PhysicalData.UM = gpsApplication.getString(R.string.UM_nm);
+ return(_PhysicalData);
+ }
+ }
+ return(_PhysicalData);
+ }
+
+ public PhysicalData format(double Number, byte Format) {
+ PhysicalData _PhysicalData = new PhysicalData();
+ _PhysicalData.Value = "";
+ _PhysicalData.UM = "";
+
+ if (Number == NOT_AVAILABLE) return(_PhysicalData); // Returns empty fields if the data is not available
+
+ switch (Format) {
+ case FORMAT_LATITUDE: // Latitude
+ _PhysicalData.Value = gpsApplication.getPrefShowDecimalCoordinates() ?
+ String.format("%.9f", Math.abs(Number)) : Location.convert(Math.abs(Number), Location.FORMAT_SECONDS);
+ _PhysicalData.UM = Number >= 0 ? gpsApplication.getString(R.string.north) : gpsApplication.getString(R.string.south);
+ return(_PhysicalData);
+
+ case FORMAT_LONGITUDE: // Longitude
+ _PhysicalData.Value = gpsApplication.getPrefShowDecimalCoordinates() ?
+ String.format("%.9f", Math.abs(Number)) : Location.convert(Math.abs(Number), Location.FORMAT_SECONDS);
+ _PhysicalData.UM = Number >= 0 ?
+ gpsApplication.getString(R.string.east) : gpsApplication.getString(R.string.west);
+ return(_PhysicalData);
+
+ case FORMAT_ALTITUDE: // Altitude
+ switch (gpsApplication.getPrefUM()) {
+ case UM_METRIC_KMH:
+ case UM_METRIC_MS:
+ _PhysicalData.Value = String.valueOf(Math.round(Number));
+ _PhysicalData.UM = gpsApplication.getString(R.string.UM_m);
+ return(_PhysicalData);
+ case UM_IMPERIAL_MPH:
+ case UM_IMPERIAL_FPS:
+ case UM_NAUTICAL_KN:
+ case UM_NAUTICAL_MPH:
+ _PhysicalData.Value = String.valueOf(Math.round(Number * M_TO_FT));
+ _PhysicalData.UM = gpsApplication.getString(R.string.UM_ft);
+ return(_PhysicalData);
+ }
+ }
+ return(_PhysicalData);
+ }
+
+ public PhysicalData format(long Number, byte Format) {
+ PhysicalData _PhysicalData = new PhysicalData();
+ _PhysicalData.Value = "";
+ _PhysicalData.UM = "";
+
+ if (Number == NOT_AVAILABLE) return(_PhysicalData); // Returns empty fields if the data is not available
+
+ switch (Format) {
+ case FORMAT_DURATION: // Durations
+ long time = Number / 1000;
+ String seconds = Integer.toString((int) (time % 60));
+ String minutes = Integer.toString((int) ((time % 3600) / 60));
+ String hours = Integer.toString((int) (time / 3600));
+ for (int i = 0; i < 2; i++) {
+ if (seconds.length() < 2) {
+ seconds = "0" + seconds;
+ }
+ if (minutes.length() < 2) {
+ minutes = "0" + minutes;
+ }
+ if (hours.length() < 2) {
+ hours = "0" + hours;
+ }
+ }
+ _PhysicalData.Value = hours.equals("00") ? minutes + ":" + seconds : hours + ":" + minutes + ":" + seconds;
+ return(_PhysicalData);
+
+ case FORMAT_TIME: // Timestamps
+ SimpleDateFormat dfdTime = new SimpleDateFormat("HH:mm:ss"); // date and time formatter
+ dfdTime.setTimeZone(TimeZone.getTimeZone("GMT"));
+ _PhysicalData.Value = dfdTime.format(Number);
+ return(_PhysicalData);
+ }
+ return(_PhysicalData);
+ }
+}
diff --git a/app/src/main/java/Satellite/SpikesChecker.java b/app/src/main/java/Satellite/SpikesChecker.java
new file mode 100644
index 0000000..5ad63a9
--- /dev/null
+++ b/app/src/main/java/Satellite/SpikesChecker.java
@@ -0,0 +1,76 @@
+/*
+ * SpikesChecker - Java Class for Android
+ * Created by G.Capelli (BasicAirData) on 15/9/2016
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package Satellite;
+
+
+class SpikesChecker {
+ private static final int NOT_AVAILABLE = -100000;
+
+ private long Good_Time = NOT_AVAILABLE; // The time of the last good value
+
+ private double Prev_Altitude = NOT_AVAILABLE; // the previous data loaded
+ private long Prev_Time = NOT_AVAILABLE;
+ private float Prev_VerticalSpeed = NOT_AVAILABLE;
+
+ private double New_Altitude = NOT_AVAILABLE; // the new (current) data loaded
+ private long New_Time = NOT_AVAILABLE;
+ private float New_VerticalSpeed = NOT_AVAILABLE;
+
+ private long Time_Interval = NOT_AVAILABLE; // Interval between fixes (in seconds)
+ private float VerticalAcceleration;
+
+ private float MAX_ACCELERATION; // The maximum vertical acceleration allowed
+ private int STABILIZATION_TIME = 4; // Stabilization window, in seconds. It must be > 0
+
+ // Constructor
+ SpikesChecker(float max_acceleration, int Stabilization_Time) {
+ MAX_ACCELERATION = max_acceleration;
+ STABILIZATION_TIME = Stabilization_Time;
+ }
+
+ void load(long Time, double Altitude) {
+ if (Time > New_Time) {
+ Prev_Time = New_Time;
+ New_Time = Time;
+ Prev_Altitude = New_Altitude;
+ Prev_VerticalSpeed = New_VerticalSpeed;
+ }
+
+ Time_Interval = Prev_Time != NOT_AVAILABLE ? (New_Time - Prev_Time) / 1000 : NOT_AVAILABLE;
+ New_Altitude = Altitude;
+
+ if ((Time_Interval > 0) && (Prev_Altitude != NOT_AVAILABLE)) {
+ New_VerticalSpeed = (float) (New_Altitude - Prev_Altitude) / Time_Interval;
+
+ if (Prev_VerticalSpeed != NOT_AVAILABLE) {
+ if (Time_Interval > 1000) VerticalAcceleration = NOT_AVAILABLE; // Prevent Vertical Acceleration value from exploding
+ else VerticalAcceleration = 2 * (-Prev_VerticalSpeed * Time_Interval + (float)(New_Altitude - Prev_Altitude)) / (Time_Interval * Time_Interval);
+ }
+ }
+
+ if (Math.abs(VerticalAcceleration) >= MAX_ACCELERATION) Good_Time = New_Time ;
+
+ //Log.w("myApp", "[#] SpikesChecker.java - Vertical Acceleration = " + VerticalAcceleration);
+ //Log.w("myApp", "[#] SpikesChecker.java - Validation window = " + (New_Time - Good_Time) / 1000);
+ }
+
+ boolean isValid() {
+ return (New_Time - Good_Time) / 1000 >= STABILIZATION_TIME;
+ }
+}
diff --git a/app/src/main/java/Satellite/Track.java b/app/src/main/java/Satellite/Track.java
new file mode 100644
index 0000000..5d6caf4
--- /dev/null
+++ b/app/src/main/java/Satellite/Track.java
@@ -0,0 +1,724 @@
+
+package Satellite;
+
+import android.location.Location;
+
+import java.text.SimpleDateFormat;
+
+public class Track {
+
+ // Constants
+ private static final int NOT_AVAILABLE = -100000;
+ private static final double MIN_ALTITUDE_STEP = 8.0;
+ private static final float MOVEMENT_SPEED_THRESHOLD = 0.5f; // The minimum speed (in m/s) to consider the user in movement
+ private static final float STANDARD_ACCURACY = 10.0f;
+ private static final float SECURITY_COEFF = 1.7f;
+
+ private final int TRACK_TYPE_STEADY = 0;
+ private final int TRACK_TYPE_WALK = 1;
+ private final int TRACK_TYPE_MOUNTAIN = 2;
+ private final int TRACK_TYPE_RUN = 3;
+ private final int TRACK_TYPE_BICYCLE = 4;
+ private final int TRACK_TYPE_CAR = 5;
+ private final int TRACK_TYPE_FLIGHT = 6;
+ private final int TRACK_TYPE_ND = NOT_AVAILABLE;
+
+ // Variables
+ private long id; // Saved in DB
+ private String Name = ""; // Saved in DB
+
+ private double Start_Latitude = NOT_AVAILABLE; // Saved in DB
+ private double Start_Longitude = NOT_AVAILABLE; // Saved in DB
+ private double Start_Altitude = NOT_AVAILABLE; // Saved in DB
+ private double Start_EGMAltitudeCorrection = NOT_AVAILABLE;
+ private float Start_Accuracy = STANDARD_ACCURACY;// Saved in DB
+ private float Start_Speed = NOT_AVAILABLE; // Saved in DB
+ private long Start_Time = NOT_AVAILABLE; // Saved in DB
+
+ private long LastFix_Time = NOT_AVAILABLE; // Saved in DB
+
+ private double End_Latitude = NOT_AVAILABLE; // Saved in DB
+ private double End_Longitude = NOT_AVAILABLE; // Saved in DB
+ private double End_Altitude = NOT_AVAILABLE; // Saved in DB
+ private double End_EGMAltitudeCorrection = NOT_AVAILABLE;
+ private float End_Accuracy = STANDARD_ACCURACY;// Saved in DB
+ private float End_Speed = NOT_AVAILABLE; // Saved in DB
+ private long End_Time = NOT_AVAILABLE; // Saved in DB
+
+ private double LastStepDistance_Latitude = NOT_AVAILABLE; // Saved in DB
+ private double LastStepDistance_Longitude = NOT_AVAILABLE; // Saved in DB
+ private float LastStepDistance_Accuracy = STANDARD_ACCURACY;// Saved in DB
+
+ private double LastStepAltitude_Altitude = NOT_AVAILABLE; // Saved in DB
+ private float LastStepAltitude_Accuracy = STANDARD_ACCURACY;// Saved in DB
+
+ private double Min_Latitude = NOT_AVAILABLE; // Saved in DB
+ private double Min_Longitude = NOT_AVAILABLE; // Saved in DB
+
+ private double Max_Latitude = NOT_AVAILABLE; // Saved in DB
+ private double Max_Longitude = NOT_AVAILABLE; // Saved in DB
+
+ private long Duration = NOT_AVAILABLE; // Saved in DB
+ private long Duration_Moving = NOT_AVAILABLE; // Saved in DB
+
+ private float Distance = NOT_AVAILABLE; // Saved in DB
+ private float DistanceInProgress = NOT_AVAILABLE; // Saved in DB
+ private long DistanceLastAltitude = NOT_AVAILABLE; // Saved in DB
+
+ private double Altitude_Up = NOT_AVAILABLE; // Saved in DB
+ private double Altitude_Down = NOT_AVAILABLE; // Saved in DB
+ private double Altitude_InProgress = NOT_AVAILABLE; // Saved in DB
+
+ private float SpeedMax = NOT_AVAILABLE; // Saved in DB
+ private float SpeedAverage = NOT_AVAILABLE; // Saved in DB
+ private float SpeedAverageMoving = NOT_AVAILABLE; // Saved in DB
+
+ private long NumberOfLocations = 0; // Saved in DB
+ private long NumberOfPlacemarks = 0; // Saved in DB
+
+ private int ValidMap = 1; // Saved in DB
+ // 1 = Map extents valid, OK generation of Thumb
+ // 0 = Do not generate thumb (track crosses antimeridian)
+
+ private int Type = TRACK_TYPE_ND; // Saved in DB
+
+ // True if the card view is selected
+ private boolean Selected = false;
+
+ // The altitude validator (the anti spikes filter):
+ // - Max Acceleration = 12 m/s^2
+ // - Stabilization time = 4 s
+ private SpikesChecker AltitudeFilter = new SpikesChecker(12, 4);
+
+ public void add(LocationExtended location) {
+ if (NumberOfLocations == 0) {
+ // Init "Start" variables
+ Start_Latitude = location.getLocation().getLatitude();
+ Start_Longitude = location.getLocation().getLongitude();
+ if (location.getLocation().hasAltitude()) {
+ Start_Altitude = location.getLocation().getAltitude();
+ } else {
+ Start_Altitude = NOT_AVAILABLE;
+ }
+ Start_EGMAltitudeCorrection = location.getAltitudeEGM96Correction();
+ Start_Speed = location.getLocation().hasSpeed() ? location.getLocation().getSpeed() : NOT_AVAILABLE;
+ Start_Accuracy = location.getLocation().hasAccuracy() ? location.getLocation().getAccuracy() : STANDARD_ACCURACY;
+ Start_Time = location.getLocation().getTime();
+
+ LastStepDistance_Latitude = Start_Latitude;
+ LastStepDistance_Longitude = Start_Longitude;
+ LastStepDistance_Accuracy = Start_Accuracy;
+
+ Max_Latitude = Start_Latitude;
+ Max_Longitude = Start_Longitude;
+ Min_Latitude = Start_Latitude;
+ Min_Longitude = Start_Longitude;
+
+ if (Name.equals("")) {
+ SimpleDateFormat df2 = new SimpleDateFormat("yyyyMMdd-HHmmss");
+ Name = df2.format(Start_Time);
+ }
+
+ LastFix_Time = Start_Time;
+ End_Time = Start_Time;
+
+ Duration_Moving = 0;
+ Duration = 0;
+ Distance = 0;
+ }
+
+ LastFix_Time = End_Time;
+
+ End_Latitude = location.getLocation().getLatitude();
+ End_Longitude = location.getLocation().getLongitude();
+ if (location.getLocation().hasAltitude()) {
+ End_Altitude = location.getLocation().getAltitude();
+ } else {
+ End_Altitude = NOT_AVAILABLE;
+ }
+ End_EGMAltitudeCorrection = location.getAltitudeEGM96Correction();
+
+ End_Speed = location.getLocation().hasSpeed() ? location.getLocation().getSpeed() : NOT_AVAILABLE;
+ End_Accuracy = location.getLocation().hasAccuracy() ? location.getLocation().getAccuracy() : STANDARD_ACCURACY;
+ End_Time = location.getLocation().getTime();
+
+ if (End_EGMAltitudeCorrection == NOT_AVAILABLE) getEnd_EGMAltitudeCorrection();
+ if (Start_EGMAltitudeCorrection == NOT_AVAILABLE) getStart_EGMAltitudeCorrection();
+
+ // ---------------------------------------------- Load the new value into antispikes filter
+ if (End_Altitude != NOT_AVAILABLE) AltitudeFilter.load(End_Time, End_Altitude);
+
+ // ------------------------------------------------------------- Coords for thumb and stats
+
+ if (ValidMap != 0) {
+ if (End_Latitude > Max_Latitude) Max_Latitude = End_Latitude;
+ if (End_Longitude > Max_Longitude) Max_Longitude = End_Longitude;
+ if (End_Latitude < Min_Latitude) Min_Latitude = End_Latitude;
+ if (End_Longitude < Min_Longitude) Min_Longitude = End_Longitude;
+
+ if (Math.abs(LastStepDistance_Longitude - End_Longitude) > 90) ValidMap = 0;
+ // YOU PASS FROM -180 TO +180, OR REVERSE. iN THE PACIFIC OCEAN.
+ // in that case the app doesn't generate the thumb map.
+ }
+
+ // ---------------------------------------------------------------------------------- Times
+
+ Duration = End_Time - Start_Time;
+ if (End_Speed >= MOVEMENT_SPEED_THRESHOLD) Duration_Moving += End_Time - LastFix_Time;
+
+ // --------------------------- Spaces (Distances) increment if distance > sum of accuracies
+
+ // -- Temp locations for "DistanceTo"
+ Location LastStepDistanceLoc = new Location("TEMP");
+ LastStepDistanceLoc.setLatitude(LastStepDistance_Latitude);
+ LastStepDistanceLoc.setLongitude(LastStepDistance_Longitude);
+
+ Location EndLoc = new Location("TEMP");
+ EndLoc.setLatitude(End_Latitude);
+ EndLoc.setLongitude(End_Longitude);
+ // -----------------------------------
+
+ DistanceInProgress = LastStepDistanceLoc.distanceTo(EndLoc);
+ float DeltaDistancePlusAccuracy = DistanceInProgress + End_Accuracy;
+
+ if (DeltaDistancePlusAccuracy < DistanceInProgress + End_Accuracy) {
+ LastStepDistance_Accuracy = DeltaDistancePlusAccuracy;
+ //Log.w("myApp", "[#] Track.java - LastStepDistance_Accuracy updated to " + LastStepDistance_Accuracy );
+ }
+
+ if (DistanceInProgress > End_Accuracy + LastStepDistance_Accuracy) {
+ Distance += DistanceInProgress;
+ if (DistanceLastAltitude != NOT_AVAILABLE) DistanceLastAltitude += DistanceInProgress;
+ DistanceInProgress = 0;
+
+ LastStepDistance_Latitude = End_Latitude;
+ LastStepDistance_Longitude = End_Longitude;
+ LastStepDistance_Accuracy = End_Accuracy;
+ }
+
+ // Found a first fix with altitude!!
+ if ((End_Altitude != NOT_AVAILABLE) && (DistanceLastAltitude == NOT_AVAILABLE)) {
+ DistanceLastAltitude = 0;
+ Altitude_Up = 0;
+ Altitude_Down = 0;
+ if (Start_Altitude == NOT_AVAILABLE) Start_Altitude = End_Altitude;
+ LastStepAltitude_Altitude = End_Altitude;
+ LastStepAltitude_Accuracy = End_Accuracy;
+ }
+
+ if ((LastStepAltitude_Altitude != NOT_AVAILABLE) && (End_Altitude != NOT_AVAILABLE)) {
+ Altitude_InProgress = End_Altitude - LastStepAltitude_Altitude;
+ // Improve last step accuracy in case of new data elements:
+ float DeltaAltitudePlusAccuracy = (float) Math.abs(Altitude_InProgress) + End_Accuracy;
+ if (DeltaAltitudePlusAccuracy <= LastStepAltitude_Accuracy) {
+ LastStepAltitude_Accuracy = DeltaAltitudePlusAccuracy;
+ DistanceLastAltitude = 0;
+ //Log.w("myApp", "[#] Track.java - LastStepAltitude_Accuracy updated to " + LastStepAltitude_Accuracy );
+ }
+ // Evaluate the altitude step convalidation:
+ if ((Math.abs(Altitude_InProgress) > MIN_ALTITUDE_STEP) && AltitudeFilter.isValid()
+ && ((float) Math.abs(Altitude_InProgress) > (SECURITY_COEFF * (LastStepAltitude_Accuracy + End_Accuracy)))) {
+ // Altitude step:
+ // increment distance only if the inclination is relevant (assume deltah=20m in max 5000m)
+ if (DistanceLastAltitude < 5000) {
+ float hypotenuse = (float) Math.sqrt((double) (DistanceLastAltitude * DistanceLastAltitude) + (Altitude_InProgress * Altitude_InProgress));
+ Distance = Distance + hypotenuse - DistanceLastAltitude;
+ //Log.w("myApp", "[#] Track.java - Distance += " + (hypotenuse - DistanceLastAltitude));
+ }
+ //Reset variables
+ LastStepAltitude_Altitude = End_Altitude;
+ LastStepAltitude_Accuracy = End_Accuracy;
+ DistanceLastAltitude = 0;
+
+ if (Altitude_InProgress > 0) Altitude_Up += Altitude_InProgress; // Increment the correct value of Altitude UP/DOWN
+ else Altitude_Down -= Altitude_InProgress;
+ Altitude_InProgress = 0;
+ }
+
+ }
+
+ // --------------------------------------------------------------------------------- Speeds
+
+ if ((End_Speed != NOT_AVAILABLE) && (End_Speed > SpeedMax)) SpeedMax = End_Speed;
+ if (Duration > 0) SpeedAverage = (Distance + DistanceInProgress) / (((float) Duration) / 1000f);
+ if (Duration_Moving > 0) SpeedAverageMoving = (Distance + DistanceInProgress) / (((float) Duration_Moving) / 1000f);
+ NumberOfLocations++;
+ }
+
+ // Empty constructor
+ public Track(){
+ }
+
+ // constructor
+ public Track(String Name){
+ this.Name = Name;
+ }
+
+ public void FromDB(long id, String Name, String From, String To,
+ double Start_Latitude, double Start_Longitude, double Start_Altitude,
+ float Start_Accuracy, float Start_Speed, long Start_Time, long LastFix_Time,
+ double End_Latitude, double End_Longitude, double End_Altitude,
+ float End_Accuracy, float End_Speed, long End_Time,
+ double LastStepDistance_Latitude, double LastStepDistance_Longitude, float LastStepDistance_Accuracy,
+ double LastStepAltitude_Altitude, float LastStepAltitude_Accuracy,
+ double Min_Latitude, double Min_Longitude,
+ double Max_Latitude, double Max_Longitude,
+ long Duration, long Duration_Moving, float Distance, float DistanceInProgress,
+ long DistanceLastAltitude, double Altitude_Up, double Altitude_Down,
+ double Altitude_InProgress, float SpeedMax, float SpeedAverage,
+ float SpeedAverageMoving, long NumberOfLocations, long NumberOfPlacemarks,
+ int ValidMap, int Type) {
+ this.id = id;
+ this.Name = Name;
+
+ this.Start_Latitude = Start_Latitude;
+ this.Start_Longitude = Start_Longitude;
+ this.Start_Altitude = Start_Altitude;
+ this.Start_Accuracy = Start_Accuracy;
+ this.Start_Speed = Start_Speed;
+ this.Start_Time = Start_Time;
+
+ this.LastFix_Time = LastFix_Time;
+
+ this.End_Latitude = End_Latitude;
+ this.End_Longitude = End_Longitude;
+ this.End_Altitude = End_Altitude;
+ this.End_Accuracy = End_Accuracy;
+ this.End_Speed = End_Speed;
+ this.End_Time = End_Time;
+
+ this.LastStepDistance_Latitude = LastStepDistance_Latitude;
+ this.LastStepDistance_Longitude = LastStepDistance_Longitude;
+ this.LastStepDistance_Accuracy = LastStepDistance_Accuracy;
+
+ this.LastStepAltitude_Altitude = LastStepAltitude_Altitude;
+ this.LastStepAltitude_Accuracy = LastStepAltitude_Accuracy;
+
+ this.Min_Latitude = Min_Latitude;
+ this.Min_Longitude = Min_Longitude;
+
+ this.Max_Latitude = Max_Latitude;
+ this.Max_Longitude = Max_Longitude;
+
+ this.Duration = Duration;
+ this.Duration_Moving = Duration_Moving;
+
+ this.Distance = Distance;
+ this.DistanceInProgress = DistanceInProgress;
+ this.DistanceLastAltitude = DistanceLastAltitude;
+
+ this.Altitude_Up = Altitude_Up;
+ this.Altitude_Down = Altitude_Down;
+ this.Altitude_InProgress = Altitude_InProgress;
+
+ this.SpeedMax = SpeedMax;
+ this.SpeedAverage = SpeedAverage;
+ this.SpeedAverageMoving = SpeedAverageMoving;
+
+ this.NumberOfLocations = NumberOfLocations;
+ this.NumberOfPlacemarks = NumberOfPlacemarks;
+
+ this.ValidMap = ValidMap;
+ this.Type = Type;
+
+ EGM96 egm96 = EGM96.getInstance();
+ if (egm96 != null) {
+ if (egm96.isEGMGridLoaded()) {
+ if (Start_Latitude != NOT_AVAILABLE) Start_EGMAltitudeCorrection = egm96.getEGMCorrection(Start_Latitude, Start_Longitude);
+ if (End_Latitude != NOT_AVAILABLE) End_EGMAltitudeCorrection = egm96.getEGMCorrection(End_Latitude, End_Longitude);
+ }
+ }
+ }
+
+
+ // ------------------------------------------------------------------------ Getters and Setters
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return Name;
+ }
+
+ public void setName(String name) {
+ Name = name;
+ }
+
+ public double getStart_Latitude() {
+ return Start_Latitude;
+ }
+
+ public double getStart_Longitude() {
+ return Start_Longitude;
+ }
+
+ public double getStart_Altitude() {
+ return Start_Altitude;
+ }
+
+ public double getStart_EGMAltitudeCorrection() {
+
+ if (Start_EGMAltitudeCorrection == NOT_AVAILABLE) {
+ EGM96 egm96 = EGM96.getInstance();
+ if (egm96 != null) {
+ if (egm96.isEGMGridLoaded()) {
+ if (Start_Latitude != NOT_AVAILABLE)
+ Start_EGMAltitudeCorrection = egm96.getEGMCorrection(Start_Latitude, Start_Longitude);
+ }
+ }
+ }
+ return Start_EGMAltitudeCorrection;
+ }
+
+ public float getStart_Accuracy() {
+ return Start_Accuracy;
+ }
+
+ public float getStart_Speed() {
+ return Start_Speed;
+ }
+
+ public long getStart_Time() {
+ return Start_Time;
+ }
+
+ public long getLastFix_Time() {
+ return LastFix_Time;
+ }
+
+ public double getEnd_Latitude() {
+ return End_Latitude;
+ }
+
+ public double getEnd_Longitude() {
+ return End_Longitude;
+ }
+
+ public double getEnd_Altitude() {
+ return End_Altitude;
+ }
+
+ public double getEnd_EGMAltitudeCorrection() {
+ if (End_EGMAltitudeCorrection == NOT_AVAILABLE) {
+ EGM96 egm96 = EGM96.getInstance();
+ if (egm96 != null) {
+ if (egm96.isEGMGridLoaded()) {
+ if (End_Latitude != NOT_AVAILABLE)
+ End_EGMAltitudeCorrection = egm96.getEGMCorrection(End_Latitude, End_Longitude);
+ }
+ }
+ }
+ return End_EGMAltitudeCorrection;
+ }
+
+ public float getEnd_Accuracy() {
+ return End_Accuracy;
+ }
+
+ public float getEnd_Speed() {
+ return End_Speed;
+ }
+
+ public long getEnd_Time() {
+ return End_Time;
+ }
+
+ public double getLastStepDistance_Latitude() {
+ return LastStepDistance_Latitude;
+ }
+
+ public double getLastStepDistance_Longitude() {
+ return LastStepDistance_Longitude;
+ }
+
+ public float getLastStepDistance_Accuracy() {
+ return LastStepDistance_Accuracy;
+ }
+
+ public double getLastStepAltitude_Altitude() {
+ return LastStepAltitude_Altitude;
+ }
+
+ public float getLastStepAltitude_Accuracy() {
+ return LastStepAltitude_Accuracy;
+ }
+
+ public double getMin_Latitude() {
+ return Min_Latitude;
+ }
+
+ public double getMin_Longitude() {
+ return Min_Longitude;
+ }
+
+ public double getMax_Latitude() {
+ return Max_Latitude;
+ }
+
+ public double getMax_Longitude() {
+ return Max_Longitude;
+ }
+
+ public long getDuration() {
+ return Duration;
+ }
+
+ public long getDuration_Moving() {
+ return Duration_Moving;
+ }
+
+ public float getDistance() {
+ return Distance;
+ }
+
+ public float getDistanceInProgress() {
+ return DistanceInProgress;
+ }
+
+ public long getDistanceLastAltitude() {
+ return DistanceLastAltitude;
+ }
+
+ public double getAltitude_Up() {
+ return Altitude_Up;
+ }
+
+ public double getAltitude_Down() {
+ return Altitude_Down;
+ }
+
+ public double getAltitude_InProgress() {
+ return Altitude_InProgress;
+ }
+
+ public float getSpeedMax() {
+ return SpeedMax;
+ }
+
+ public float getSpeedAverage() {
+ return SpeedAverage;
+ }
+
+ public float getSpeedAverageMoving() {
+ return SpeedAverageMoving;
+ }
+
+ public long getNumberOfLocations() {
+ return NumberOfLocations;
+ }
+
+ public long getNumberOfPlacemarks() {
+ return NumberOfPlacemarks;
+ }
+
+ public int getValidMap() {
+ return ValidMap;
+ }
+
+ public int getType() {
+ return Type;
+ }
+
+ public boolean isSelected() {
+ return Selected;
+ }
+
+ public void setSelected(boolean selected) {
+ Selected = selected;
+ }
+
+ // --------------------------------------------------------------------------------------------
+
+ public boolean isValidAltitude() {
+ return AltitudeFilter.isValid();
+ }
+
+ public long addPlacemark(LocationExtended location) {
+ this.NumberOfPlacemarks++ ;
+
+ if (Name.equals("")) {
+ SimpleDateFormat df2 = new SimpleDateFormat("yyyyMMdd-HHmmss");
+ Name = df2.format(location.getLocation().getTime());
+ }
+
+ return NumberOfPlacemarks;
+ }
+
+ public float getEstimatedDistance(){
+ if (NumberOfLocations == 0) return NOT_AVAILABLE;
+ if (NumberOfLocations == 1) return 0;
+ return Distance + DistanceInProgress;
+ }
+
+
+ public double getEstimatedAltitudeUp(boolean EGMCorrection){
+ // Retrieve EGM Corrections if available
+ if ((Start_EGMAltitudeCorrection == NOT_AVAILABLE) || (End_EGMAltitudeCorrection == NOT_AVAILABLE)) {
+ EGM96 egm96 = EGM96.getInstance();
+ if (egm96 != null) {
+ if (egm96.isEGMGridLoaded()) {
+ if (Start_Latitude != NOT_AVAILABLE) Start_EGMAltitudeCorrection = egm96.getEGMCorrection(Start_Latitude, Start_Longitude);
+ if (End_Latitude != NOT_AVAILABLE) End_EGMAltitudeCorrection = egm96.getEGMCorrection(End_Latitude, End_Longitude);
+ }
+ }
+ }
+ double egmcorr = 0;
+ if ((EGMCorrection) && ((Start_EGMAltitudeCorrection != NOT_AVAILABLE) && (End_EGMAltitudeCorrection != NOT_AVAILABLE))) {
+ egmcorr = Start_EGMAltitudeCorrection - End_EGMAltitudeCorrection;
+ }
+ double dresultUp = Altitude_InProgress > 0 ? Altitude_Up + Altitude_InProgress : Altitude_Up;
+ dresultUp -= egmcorr < 0 ? egmcorr : 0;
+ double dresultDown = Altitude_InProgress < 0 ? Altitude_Down - Altitude_InProgress : Altitude_Down;
+ dresultDown -= egmcorr > 0 ? egmcorr : 0;
+
+ if (dresultUp < 0) {
+ dresultDown -= dresultUp;
+ dresultUp = 0;
+ }
+ if (dresultDown < 0) {
+ dresultUp -= dresultDown;
+ //dresultDown = 0;
+ }
+ return dresultUp;
+ }
+
+
+ public double getEstimatedAltitudeDown(boolean EGMCorrection){
+ // Retrieve EGM Corrections if available
+ if ((Start_EGMAltitudeCorrection == NOT_AVAILABLE) || (End_EGMAltitudeCorrection == NOT_AVAILABLE)) {
+ EGM96 egm96 = EGM96.getInstance();
+ if (egm96 != null) {
+ if (egm96.isEGMGridLoaded()) {
+ if (Start_Latitude != NOT_AVAILABLE) Start_EGMAltitudeCorrection = egm96.getEGMCorrection(Start_Latitude, Start_Longitude);
+ if (End_Latitude != NOT_AVAILABLE) End_EGMAltitudeCorrection = egm96.getEGMCorrection(End_Latitude, End_Longitude);
+ }
+ }
+ }
+ double egmcorr = 0;
+ if ((EGMCorrection) && ((Start_EGMAltitudeCorrection != NOT_AVAILABLE) && (End_EGMAltitudeCorrection != NOT_AVAILABLE))) {
+ egmcorr = Start_EGMAltitudeCorrection - End_EGMAltitudeCorrection;
+ }
+ double dresultUp = Altitude_InProgress > 0 ? Altitude_Up + Altitude_InProgress : Altitude_Up;
+ dresultUp -= egmcorr < 0 ? egmcorr : 0;
+ double dresultDown = Altitude_InProgress < 0 ? Altitude_Down - Altitude_InProgress : Altitude_Down;
+ dresultDown -= egmcorr > 0 ? egmcorr : 0;
+
+ if (dresultUp < 0) {
+ dresultDown -= dresultUp;
+ dresultUp = 0;
+ }
+ if (dresultDown < 0) {
+ //dresultUp -= dresultDown;
+ dresultDown = 0;
+ }
+ return dresultDown;
+ }
+
+ public double getEstimatedAltitudeGap(boolean EGMCorrection){
+ return getEstimatedAltitudeUp(EGMCorrection) - getEstimatedAltitudeDown(EGMCorrection);
+ }
+
+
+ public float getBearing() {
+ if (End_Latitude != NOT_AVAILABLE) {
+ if (((Start_Latitude == End_Latitude) && (Start_Longitude == End_Longitude)) || (Distance == 0))
+ return NOT_AVAILABLE;
+ Location EndLoc = new Location("TEMP");
+ EndLoc.setLatitude(End_Latitude);
+ EndLoc.setLongitude(End_Longitude);
+ Location StartLoc = new Location("TEMP");
+ StartLoc.setLatitude(Start_Latitude);
+ StartLoc.setLongitude(Start_Longitude);
+ float BTo = StartLoc.bearingTo(EndLoc);
+ if (BTo < 0) BTo += 360f;
+ return BTo;
+ }
+ return NOT_AVAILABLE;
+ }
+
+
+ // Returns the time, based on preferences (Total or Moving)
+ public long getPrefTime() {
+ GPSApplication gpsApplication = GPSApplication.getInstance();
+ int pTime = gpsApplication.getPrefShowTrackStatsType();
+ switch (pTime) {
+ case 0: // Total based
+ return Duration;
+ case 1: // Moving based
+ return Duration_Moving;
+ default:
+ return Duration;
+ }
+ }
+
+
+ // Returns the average speed, based on preferences (Total or Moving)
+ public float getPrefSpeedAverage() {
+ if (NumberOfLocations == 0) return NOT_AVAILABLE;
+ GPSApplication gpsApplication = GPSApplication.getInstance();
+ int pTime = gpsApplication.getPrefShowTrackStatsType();
+ switch (pTime) {
+ case 0: // Total based
+ return SpeedAverage;
+ case 1: // Moving based
+ return SpeedAverageMoving;
+ default:
+ return SpeedAverage;
+ }
+ }
+
+
+ public int getTrackType() {
+
+ //if (Type != TRACK_TYPE_ND) return Type;
+
+ if ((Distance == NOT_AVAILABLE) || (SpeedMax == NOT_AVAILABLE)) {
+ if (NumberOfPlacemarks == 0) return TRACK_TYPE_ND;
+ else return TRACK_TYPE_STEADY;
+ }
+ if ((Distance < 15.0f) || (SpeedMax == 0.0f) || (SpeedAverageMoving == NOT_AVAILABLE)) return TRACK_TYPE_STEADY;
+ if (SpeedMax < (7.0f / 3.6f)) {
+ if ((Altitude_Up != NOT_AVAILABLE) && (Altitude_Down != NOT_AVAILABLE))
+ if ((Altitude_Down + Altitude_Up > (0.1f * Distance)) && (Distance > 500.0f)) return TRACK_TYPE_MOUNTAIN;
+ else return TRACK_TYPE_WALK;
+ }
+ if (SpeedMax < (15.0f / 3.6f)) {
+ if (SpeedAverageMoving > 8.0f / 3.6f) return TRACK_TYPE_RUN;
+ else {
+ if ((Altitude_Up != NOT_AVAILABLE) && (Altitude_Down != NOT_AVAILABLE))
+ if ((Altitude_Down + Altitude_Up > (0.1f * Distance)) && (Distance > 500.0f)) return TRACK_TYPE_MOUNTAIN;
+ else return TRACK_TYPE_WALK;
+ }
+ }
+ if (SpeedMax < (50.0f / 3.6f)) {
+ if ((SpeedAverageMoving + SpeedMax) / 2 > 35.0f / 3.6f) return TRACK_TYPE_CAR;
+ if ((SpeedAverageMoving + SpeedMax) / 2 > 20.0f / 3.6) return TRACK_TYPE_BICYCLE;
+ else if ((SpeedAverageMoving + SpeedMax) / 2 > 12.0f / 3.6f) return TRACK_TYPE_RUN;
+ else {
+ if ((Altitude_Up != NOT_AVAILABLE) && (Altitude_Down != NOT_AVAILABLE))
+ if ((Altitude_Down + Altitude_Up > (0.1f * Distance)) && (Distance > 500.0f))
+ return TRACK_TYPE_MOUNTAIN;
+ else return TRACK_TYPE_WALK;
+ }
+ /*
+ if (SpeedAverageMoving > 20.0f / 3.6f) return TRACK_TYPE_CAR;
+ if (SpeedAverageMoving > 12.0f / 3.6) return TRACK_TYPE_BICYCLE;
+ else if (SpeedAverageMoving > 8.0f / 3.6f) return TRACK_TYPE_RUN;
+ else {
+ if ((Altitude_Up != NOT_AVAILABLE) && (Altitude_Down != NOT_AVAILABLE))
+ if ((Altitude_Down + Altitude_Up > (0.1f * Distance)) && (Distance > 500.0f))
+ return TRACK_TYPE_MOUNTAIN;
+ else return TRACK_TYPE_WALK;
+ }*/
+ }
+ if ((Altitude_Up != NOT_AVAILABLE) && (Altitude_Down != NOT_AVAILABLE))
+ if ((Altitude_Down + Altitude_Up > 5000.0) && (SpeedMax > 300.0f / 3.6f)) return TRACK_TYPE_FLIGHT;
+
+ return TRACK_TYPE_CAR;
+ }
+}
diff --git a/app/src/main/java/armeasure/Armeasure_save.java b/app/src/main/java/armeasure/Armeasure_save.java
new file mode 100644
index 0000000..c4e087c
--- /dev/null
+++ b/app/src/main/java/armeasure/Armeasure_save.java
@@ -0,0 +1,330 @@
+package armeasure;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Build.VERSION_CODES;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.SeekBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.app.AlertDialog.Builder;
+
+
+import com.google.ar.core.Anchor;
+import com.google.ar.core.HitResult;
+import com.google.ar.core.Plane;
+import com.google.ar.core.Pose;
+import com.google.ar.sceneform.AnchorNode;
+import com.google.ar.sceneform.math.Vector3;
+import com.google.ar.sceneform.rendering.ModelRenderable;
+import com.google.ar.sceneform.ux.ArFragment;
+import com.google.ar.sceneform.ux.TransformableNode;
+
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+import fr.geolabs.dev.mapmint4me.R;
+
+public class Armeasure_save extends AppCompatActivity {
+
+ private static final String TAG = Armeasure_save.class.getSimpleName();
+ private static final double MIN_OPENGL_VERSION = 3.0;
+ private float upDistance = 0f;
+ private ArFragment arFragment;
+ private ModelRenderable andyRenderable;
+ private AnchorNode myanchornode;
+ private DecimalFormat form_numbers = new DecimalFormat("#0.00 m");
+
+ private Anchor anchor1 = null, anchor2 = null;
+
+ private HitResult myhit;
+
+ private TextView text;
+ private SeekBar sk_height_control;
+ private Button btn_save, btn_width, btn_height;
+ private Button btn_share;
+
+ List anchorNodes = new ArrayList<>();
+
+ private boolean measure_height = false;
+ private ArrayList arl_saved = new ArrayList();
+
+ private float fl_measurement = 0.0f;
+
+ private String message;
+
+
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (!checkIsSupportedDeviceOrFinish(this)) {
+ return;
+ }
+ try
+ {
+ this.getSupportActionBar().hide();
+ }
+ catch (NullPointerException e){}
+
+ setContentView(R.layout.armeasure_save);
+
+
+ arFragment = (ArFragment) getSupportFragmentManager().findFragmentById(R.id.ux_fragment);
+ text = (TextView) findViewById(R.id.text);
+
+ sk_height_control = (SeekBar) findViewById(R.id.sk_height_control);
+ btn_height = (Button) findViewById(R.id.btn_height);
+ btn_save = (Button) findViewById(R.id.btn_save);
+ btn_width = (Button) findViewById(R.id.btn_width);
+ btn_share = (Button) findViewById(R.id.btn_share);
+
+ sk_height_control.setEnabled(false);
+
+ btn_width.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ resetLayout();
+ measure_height = false;
+ // text.setText("Click the extremes you want to measure");
+ }
+ });
+
+ btn_height.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ resetLayout();
+ measure_height = true;
+
+ text.setText("Click the base of the object you want to measure");
+ }
+ });
+
+ btn_save.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if(fl_measurement != 0.0f)
+ saveDialog();
+ else
+ Toast.makeText(Armeasure_save.this, "Make a measurement before saving", Toast.LENGTH_SHORT).show();
+ }
+ });
+
+ btn_share.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if(arl_saved.size() > 0){
+ Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
+ sharingIntent.setType("text/plain");
+ String shareBody = "";
+ for(String measurement : arl_saved)
+ shareBody += measurement+"\n";
+
+ System.out.println(shareBody);
+ shareBody = shareBody.trim();
+ sharingIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, "AR Measurements");
+ sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, shareBody);
+ startActivity(Intent.createChooser(sharingIntent, "Share via"));
+ }
+ else
+ Toast.makeText(Armeasure_save.this, "Save measurements before sharing", Toast.LENGTH_SHORT).show();
+ }
+ });
+
+
+ sk_height_control.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ upDistance = progress;
+ fl_measurement = progress/100f;
+ text.setText("Height: "+form_numbers.format(fl_measurement));
+ myanchornode.setLocalScale(new Vector3(1f, progress/10f, 1f));
+ //ascend(myanchornode, upDistance);
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ }
+ });
+
+ ModelRenderable.builder()
+ .setSource(this, R.raw.cubito)
+ .build()
+ .thenAccept(renderable -> andyRenderable = renderable)
+ .exceptionally(
+ throwable -> {
+ Toast toast =
+ Toast.makeText(this, "Unable to load andy renderable", Toast.LENGTH_LONG);
+ toast.setGravity(Gravity.CENTER, 0, 0);
+ toast.show();
+ return null;
+ });
+
+ arFragment.setOnTapArPlaneListener(
+ (HitResult hitResult, Plane plane, MotionEvent motionEvent) -> {
+ if (andyRenderable == null) {
+ return;
+ }
+ myhit = hitResult;
+
+ // Create the Anchor.
+ Anchor anchor = hitResult.createAnchor();
+
+ AnchorNode anchorNode = new AnchorNode(anchor);
+
+
+ anchorNode.setParent(arFragment.getArSceneView().getScene());
+
+ if(!measure_height) {
+ if(anchor2 != null){
+ emptyAnchors();
+ }
+ if (anchor1 == null) {
+ anchor1 = anchor;
+ } else {
+ anchor2 = anchor;
+ fl_measurement = getMetersBetweenAnchors(anchor1, anchor2);
+ // text.setText("Width: " +
+ // form_numbers.format(fl_measurement));
+
+ }
+ }
+ else{
+ emptyAnchors();
+ anchor1 = anchor;
+ //text.setText("Move the slider till the cube reaches the upper base");
+ sk_height_control.setEnabled(true);
+ }
+
+ myanchornode = anchorNode;
+ anchorNodes.add(anchorNode);
+
+ // Create the transformable andy and add it to the anchor.
+ TransformableNode andy = new TransformableNode(arFragment.getTransformationSystem());
+ andy.setParent(anchorNode);
+ andy.setRenderable(andyRenderable);
+ andy.select();
+ andy.getScaleController().setEnabled(false);
+ });
+ }
+
+ /**
+ * Function to raise an object perpendicular to the ArPlane a specific distance
+ * @param an anchor belonging to the object that should be raised
+ * @param up distance in centimeters the object should be raised vertically
+ */
+ private void ascend(AnchorNode an, float up) {
+ Anchor anchor = myhit.getTrackable().createAnchor(
+ myhit.getHitPose().compose(Pose.makeTranslation(0, up / 100f, 0)));
+
+ an.setAnchor(anchor);
+ }
+
+ /**
+ * Function to return the distance in meters between two objects placed in ArPlane
+ * @param anchor1 first object's anchor
+ * @param anchor2 second object's anchor
+ * @return the distance between the two anchors in meters
+ */
+ private float getMetersBetweenAnchors(Anchor anchor1, Anchor anchor2) {
+ float[] distance_vector = anchor1.getPose().inverse()
+ .compose(anchor2.getPose()).getTranslation();
+ float totalDistanceSquared = 0;
+ for (int i = 0; i < 3; ++i)
+ totalDistanceSquared += distance_vector[i] * distance_vector[i];
+ return (float) Math.sqrt(totalDistanceSquared);
+ }
+
+
+ /**
+ * Check whether the device supports the tools required to use the measurement tools
+ * @param activity
+ * @return boolean determining whether the device is supported or not
+ */
+ private boolean checkIsSupportedDeviceOrFinish(final Activity activity) {
+ if (Build.VERSION.SDK_INT < VERSION_CODES.N) {
+ Log.e(TAG, "Sceneform requires Android N or later");
+ Toast.makeText(activity, "Sceneform requires Android N or later", Toast.LENGTH_LONG).show();
+ activity.finish();
+ return false;
+ }
+ String openGlVersionString =
+ ((ActivityManager) activity.getSystemService(Context.ACTIVITY_SERVICE))
+ .getDeviceConfigurationInfo()
+ .getGlEsVersion();
+ if (Double.parseDouble(openGlVersionString) < MIN_OPENGL_VERSION) {
+ Log.e(TAG, "Sceneform requires OpenGL ES 3.0 later");
+ Toast.makeText(activity, "Sceneform requires OpenGL ES 3.0 or later", Toast.LENGTH_LONG)
+ .show();
+ activity.finish();
+ return false;
+ }
+ return true;
+ }
+
+ private void saveDialog() {
+ Builder mBuilder = new Builder(Armeasure_save.this);
+ View mView = getLayoutInflater().inflate(R.layout.dialog_save, null);
+
+ EditText et_measure = (EditText) mView.findViewById(R.id.et_measure);
+ mBuilder.setTitle("Measurement title");
+
+ mBuilder.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ if(et_measure.length() != 0){
+ arl_saved.add(et_measure.getText()+": "+form_numbers.format(fl_measurement));
+ System.out.println(arl_saved);
+ dialogInterface.dismiss();
+ }
+ else
+ Toast.makeText(Armeasure_save.this, "Title can't be empty", Toast.LENGTH_SHORT).show();
+ }
+ });
+
+ mBuilder.setView(mView);
+ AlertDialog dialog = mBuilder.create();
+
+ dialog.show();
+ }
+
+ /**
+ * Set layout to its initial state
+ */
+ private void resetLayout(){
+ sk_height_control.setProgress(10);
+ sk_height_control.setEnabled(false);
+ measure_height = false;
+ emptyAnchors();
+ }
+
+ private void emptyAnchors(){
+ anchor1 = null;
+ anchor2 = null;
+ for (AnchorNode n : anchorNodes) {
+ arFragment.getArSceneView().getScene().removeChild(n);
+ n.getAnchor().detach();
+ n.setParent(null);
+ n = null;
+ }
+ }
+}
diff --git a/app/src/main/java/com/google/ar/core/examples/java/helloar/CameraPermissionHelper.java b/app/src/main/java/com/google/ar/core/examples/java/helloar/CameraPermissionHelper.java
new file mode 100755
index 0000000..093550a
--- /dev/null
+++ b/app/src/main/java/com/google/ar/core/examples/java/helloar/CameraPermissionHelper.java
@@ -0,0 +1,52 @@
+
+package com.google.ar.core.examples.java.helloar;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.provider.Settings;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.content.ContextCompat;
+
+/**
+ * Helper to ask camera permission.
+ */
+public class CameraPermissionHelper {
+ private static final int CAMERA_PERMISSION_CODE = 0;
+ private static final String CAMERA_PERMISSION = Manifest.permission.CAMERA;
+
+ /**
+ * Check to see we have the necessary permissions for this app.
+ */
+ public static boolean hasCameraPermission(Activity activity) {
+ return ContextCompat.checkSelfPermission(activity, CAMERA_PERMISSION)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+ /**
+ * Check to see we have the necessary permissions for this app, and ask for them if we don't.
+ */
+ public static void requestCameraPermission(Activity activity) {
+ ActivityCompat.requestPermissions(
+ activity, new String[]{CAMERA_PERMISSION}, CAMERA_PERMISSION_CODE);
+ }
+
+ /**
+ * Check to see if we need to show the rationale for this permission.
+ */
+ public static boolean shouldShowRequestPermissionRationale(Activity activity) {
+ return ActivityCompat.shouldShowRequestPermissionRationale(activity, CAMERA_PERMISSION);
+ }
+
+ /**
+ * Launch Application Setting to grant permission.
+ */
+ public static void launchPermissionSettings(Activity activity) {
+ Intent intent = new Intent();
+ intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+ intent.setData(Uri.fromParts("package", activity.getPackageName(), null));
+ activity.startActivity(intent);
+ }
+}
diff --git a/app/src/main/java/com/google/ar/core/examples/java/helloar/DisplayRotationHelper.java b/app/src/main/java/com/google/ar/core/examples/java/helloar/DisplayRotationHelper.java
new file mode 100644
index 0000000..7befd34
--- /dev/null
+++ b/app/src/main/java/com/google/ar/core/examples/java/helloar/DisplayRotationHelper.java
@@ -0,0 +1,91 @@
+
+package com.google.ar.core.examples.java.helloar;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManager.DisplayListener;
+import android.view.Display;
+import android.view.WindowManager;
+
+import com.google.ar.core.Session;
+
+/**
+ * Helper to track the display rotations. In particular, the 180 degree rotations are not notified
+ * by the onSurfaceChanged() callback, and thus they require listening to the android display
+ * events.
+ */
+public class DisplayRotationHelper implements DisplayListener {
+ private boolean viewportChanged;
+ private int viewportWidth;
+ private int viewportHeight;
+ private final Context context;
+ private final Display display;
+
+ /**
+ * Constructs the DisplayRotationHelper but does not register the listener yet.
+ *
+ * @param context the Android {@link Context}.
+ */
+ public DisplayRotationHelper(Context context) {
+ this.context = context;
+ display = context.getSystemService(WindowManager.class).getDefaultDisplay();
+ }
+
+ public void onResume() {
+ context.getSystemService(DisplayManager.class).registerDisplayListener(this, null);
+ }
+
+
+ public void onPause() {
+ context.getSystemService(DisplayManager.class).unregisterDisplayListener(this);
+ }
+
+ /**
+ * Records a change in surface dimensions. This will be later used by {@link
+ * #updateSessionIfNeeded(Session)}. Should be called from {@link
+ * android.opengl.GLSurfaceView.Renderer
+ * #onSurfaceChanged(javax.microedition.khronos.opengles.GL10, int, int)}.
+ *
+ * @param width the updated width of the surface.
+ * @param height the updated height of the surface.
+ */
+ public void onSurfaceChanged(int width, int height) {
+ viewportWidth = width;
+ viewportHeight = height;
+ viewportChanged = true;
+ }
+
+ /**
+ * Updates the session display geometry if a change was posted either by {@link
+ * #onSurfaceChanged(int, int)} call or by {@link #onDisplayChanged(int)} system callback. This
+ * function should be called explicitly before each call to {@link Session#update()}. This
+ * function will also clear the 'pending update' (viewportChanged) flag.
+ *
+ * @param session the {@link Session} object to update if display geometry changed.
+ */
+ public void updateSessionIfNeeded(Session session) {
+ if (viewportChanged) {
+ int displayRotation = display.getRotation();
+ session.setDisplayGeometry(displayRotation, viewportWidth, viewportHeight);
+ viewportChanged = false;
+ }
+ }
+
+ /**
+ * Returns the current rotation state of android display. Same as {@link Display#getRotation()}.
+ */
+ public int getRotation() {
+ return display.getRotation();
+ }
+
+ @Override
+ public void onDisplayAdded(int displayId) {}
+
+ @Override
+ public void onDisplayRemoved(int displayId) {}
+
+ @Override
+ public void onDisplayChanged(int displayId) {
+ viewportChanged = true;
+ }
+}
diff --git a/app/src/main/java/com/google/ar/core/examples/java/helloar/rendering/BackgroundRenderer.java b/app/src/main/java/com/google/ar/core/examples/java/helloar/rendering/BackgroundRenderer.java
new file mode 100755
index 0000000..ff67b7f
--- /dev/null
+++ b/app/src/main/java/com/google/ar/core/examples/java/helloar/rendering/BackgroundRenderer.java
@@ -0,0 +1,178 @@
+
+package com.google.ar.core.examples.java.helloar.rendering;
+
+import android.content.Context;
+import android.opengl.GLES11Ext;
+import android.opengl.GLES20;
+import android.opengl.GLSurfaceView;
+
+import com.google.ar.core.Frame;
+import com.google.ar.core.Session;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+import fr.geolabs.dev.mapmint4me.R;
+
+/**
+ * This class renders the AR background from camera feed. It creates and hosts the texture given to
+ * ARCore to be filled with the camera image.
+ */
+public class BackgroundRenderer {
+ private static final String TAG = BackgroundRenderer.class.getSimpleName();
+
+ private static final int COORDS_PER_VERTEX = 3;
+ private static final int TEXCOORDS_PER_VERTEX = 2;
+ private static final int FLOAT_SIZE = 4;
+
+ private FloatBuffer quadVertices;
+ private FloatBuffer quadTexCoord;
+ private FloatBuffer quadTexCoordTransformed;
+
+ private int quadProgram;
+
+ private int quadPositionParam;
+ private int quadTexCoordParam;
+ private int textureId = -1;
+
+ public BackgroundRenderer() {
+ }
+
+ public int getTextureId() {
+ return textureId;
+ }
+
+ /**
+ * Allocates and initializes OpenGL resources needed by the background renderer. Must be called on
+ * the OpenGL thread, typically in {@link GLSurfaceView.Renderer#onSurfaceCreated(GL10,
+ * EGLConfig)}.
+ *
+ * @param context Needed to access shader source.
+ */
+ public void createOnGlThread(Context context) {
+ // Generate the background texture.
+ int[] textures = new int[1];
+ GLES20.glGenTextures(1, textures, 0);
+ textureId = textures[0];
+ int textureTarget = GLES11Ext.GL_TEXTURE_EXTERNAL_OES;
+ GLES20.glBindTexture(textureTarget, textureId);
+ GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
+ GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
+ GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
+ GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);
+
+ int numVertices = 4;
+ if (numVertices != QUAD_COORDS.length / COORDS_PER_VERTEX) {
+ throw new RuntimeException("Unexpected number of vertices in BackgroundRenderer.");
+ }
+
+ ByteBuffer bbVertices = ByteBuffer.allocateDirect(QUAD_COORDS.length * FLOAT_SIZE);
+ bbVertices.order(ByteOrder.nativeOrder());
+ quadVertices = bbVertices.asFloatBuffer();
+ quadVertices.put(QUAD_COORDS);
+ quadVertices.position(0);
+
+ ByteBuffer bbTexCoords =
+ ByteBuffer.allocateDirect(numVertices * TEXCOORDS_PER_VERTEX * FLOAT_SIZE);
+ bbTexCoords.order(ByteOrder.nativeOrder());
+ quadTexCoord = bbTexCoords.asFloatBuffer();
+ quadTexCoord.put(QUAD_TEXCOORDS);
+ quadTexCoord.position(0);
+
+ ByteBuffer bbTexCoordsTransformed =
+ ByteBuffer.allocateDirect(numVertices * TEXCOORDS_PER_VERTEX * FLOAT_SIZE);
+ bbTexCoordsTransformed.order(ByteOrder.nativeOrder());
+ quadTexCoordTransformed = bbTexCoordsTransformed.asFloatBuffer();
+
+ int vertexShader =
+ ShaderUtil.loadGLShader(TAG, context, GLES20.GL_VERTEX_SHADER, R.raw.screenquad_vertex);
+ int fragmentShader =
+ ShaderUtil.loadGLShader(
+ TAG, context, GLES20.GL_FRAGMENT_SHADER, R.raw.screenquad_fragment_oes);
+
+ quadProgram = GLES20.glCreateProgram();
+ GLES20.glAttachShader(quadProgram, vertexShader);
+ GLES20.glAttachShader(quadProgram, fragmentShader);
+ GLES20.glLinkProgram(quadProgram);
+ GLES20.glUseProgram(quadProgram);
+
+ ShaderUtil.checkGLError(TAG, "Program creation");
+
+ quadPositionParam = GLES20.glGetAttribLocation(quadProgram, "a_Position");
+ quadTexCoordParam = GLES20.glGetAttribLocation(quadProgram, "a_TexCoord");
+
+ ShaderUtil.checkGLError(TAG, "Program parameters");
+ }
+
+ /**
+ * Draws the AR background image. The image will be drawn such that virtual content rendered with
+ * the matrices provided by {@link com.google.ar.core.Camera#getViewMatrix(float[], int)} and
+ * {@link com.google.ar.core.Camera#getProjectionMatrix(float[], int, float, float)} will
+ * accurately follow static physical objects. This must be called before drawing virtual
+ * content.
+ *
+ * @param frame The last {@code Frame} returned by {@link Session#update()}.
+ */
+ public void draw(Frame frame) {
+ // If display rotation changed (also includes view size change), we need to re-query the uv
+ // coordinates for the screen rect, as they may have changed as well.
+ if (frame.hasDisplayGeometryChanged()) {
+ frame.transformDisplayUvCoords(quadTexCoord, quadTexCoordTransformed);
+ }
+
+ // No need to test or write depth, the screen quad has arbitrary depth, and is expected
+ // to be drawn first.
+ GLES20.glDisable(GLES20.GL_DEPTH_TEST);
+ GLES20.glDepthMask(false);
+
+ GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId);
+
+ GLES20.glUseProgram(quadProgram);
+
+ // Set the vertex positions.
+ GLES20.glVertexAttribPointer(
+ quadPositionParam, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, 0, quadVertices);
+
+ // Set the texture coordinates.
+ GLES20.glVertexAttribPointer(
+ quadTexCoordParam,
+ TEXCOORDS_PER_VERTEX,
+ GLES20.GL_FLOAT,
+ false,
+ 0,
+ quadTexCoordTransformed);
+
+ // Enable vertex arrays
+ GLES20.glEnableVertexAttribArray(quadPositionParam);
+ GLES20.glEnableVertexAttribArray(quadTexCoordParam);
+
+ GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
+
+ // Disable vertex arrays
+ GLES20.glDisableVertexAttribArray(quadPositionParam);
+ GLES20.glDisableVertexAttribArray(quadTexCoordParam);
+
+ // Restore the depth state for further drawing.
+ GLES20.glDepthMask(true);
+ GLES20.glEnable(GLES20.GL_DEPTH_TEST);
+
+ ShaderUtil.checkGLError(TAG, "Draw");
+ }
+
+ private static final float[] QUAD_COORDS =
+ new float[]{
+ -1.0f, -1.0f, 0.0f, -1.0f, +1.0f, 0.0f, +1.0f, -1.0f, 0.0f, +1.0f, +1.0f, 0.0f,
+ };
+
+ private static final float[] QUAD_TEXCOORDS =
+ new float[]{
+ 0.0f, 1.0f,
+ 0.0f, 0.0f,
+ 1.0f, 1.0f,
+ 1.0f, 0.0f,
+ };
+}
diff --git a/app/src/main/java/com/google/ar/core/examples/java/helloar/rendering/ObjectRenderer.java b/app/src/main/java/com/google/ar/core/examples/java/helloar/rendering/ObjectRenderer.java
new file mode 100755
index 0000000..1693f65
--- /dev/null
+++ b/app/src/main/java/com/google/ar/core/examples/java/helloar/rendering/ObjectRenderer.java
@@ -0,0 +1,373 @@
+
+package com.google.ar.core.examples.java.helloar.rendering;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.opengl.GLES20;
+import android.opengl.GLUtils;
+import android.opengl.Matrix;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.nio.ShortBuffer;
+
+import de.javagl.obj.Obj;
+import de.javagl.obj.ObjData;
+import de.javagl.obj.ObjReader;
+import de.javagl.obj.ObjUtils;
+import fr.geolabs.dev.mapmint4me.R;
+
+/**
+ * Renders an object loaded from an OBJ file in OpenGL.
+ */
+public class ObjectRenderer {
+ private static final String TAG = ObjectRenderer.class.getSimpleName();
+
+ /**
+ * Blend mode.
+ *
+ * @see #setBlendMode(BlendMode)
+ */
+ public enum BlendMode {
+ /**
+ * Multiplies the destination color by the source alpha.
+ */
+ Shadow,
+ /**
+ * Normal alpha blending.
+ */
+ Grid
+ }
+
+ private static final int COORDS_PER_VERTEX = 3;
+
+ // Note: the last component must be zero to avoid applying the translational part of the matrix.
+ private static final float[] LIGHT_DIRECTION = new float[]{0.250f, 0.866f, 0.433f, 0.0f};
+ private final float[] viewLightDirection = new float[4];
+
+ // Object vertex buffer variables.
+ private int vertexBufferId;
+ private int verticesBaseAddress;
+ private int texCoordsBaseAddress;
+ private int normalsBaseAddress;
+ private int indexBufferId;
+ private int indexCount;
+
+ private int program;
+ private final int[] textures = new int[1];
+
+ // Shader location: model view projection matrix.
+ private int modelViewUniform;
+ private int modelViewProjectionUniform;
+
+ // Shader location: object attributes.
+ private int positionAttribute;
+ private int normalAttribute;
+ private int texCoordAttribute;
+
+ // Shader location: texture sampler.
+ private int textureUniform;
+
+ // Shader location: environment properties.
+ private int lightingParametersUniform;
+
+ // Shader location: material properties.
+ private int materialParametersUniform;
+
+ private BlendMode blendMode = null;
+
+ // Temporary matrices allocated here to reduce number of allocations for each frame.
+ private final float[] modelMatrix = new float[16];
+ private final float[] modelViewMatrix = new float[16];
+ private final float[] modelViewProjectionMatrix = new float[16];
+
+ // Set some default material properties to use for lighting.
+ private float ambient = 0.3f;
+ private float diffuse = 1.0f;
+ private float specular = 1.0f;
+ private float specularPower = 6.0f;
+
+ public ObjectRenderer() {
+ }
+
+ /**
+ * Creates and initializes OpenGL resources needed for rendering the model.
+ *
+ * @param context Context for loading the shader and below-named model and texture assets.
+ * @param objAssetName Name of the OBJ file containing the model geometry.
+ * @param diffuseTextureAssetName Name of the PNG file containing the diffuse texture map.
+ */
+ public void createOnGlThread(Context context, String objAssetName, String diffuseTextureAssetName)
+ throws IOException {
+ // Read the texture.
+ Bitmap textureBitmap =
+ BitmapFactory.decodeStream(context.getAssets().open(diffuseTextureAssetName));
+
+ GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+ GLES20.glGenTextures(textures.length, textures, 0);
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]);
+
+ GLES20.glTexParameteri(
+ GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR_MIPMAP_LINEAR);
+ GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
+ GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, textureBitmap, 0);
+ GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D);
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
+
+ textureBitmap.recycle();
+
+ ShaderUtil.checkGLError(TAG, "Texture loading");
+
+ // Read the obj file.
+ InputStream objInputStream = context.getAssets().open(objAssetName);
+ Obj obj = ObjReader.read(objInputStream);
+
+ // Prepare the Obj so that its structure is suitable for
+ // rendering with OpenGL:
+ // 1. Triangulate it
+ // 2. Make sure that texture coordinates are not ambiguous
+ // 3. Make sure that normals are not ambiguous
+ // 4. Convert it to single-indexed data
+ obj = ObjUtils.convertToRenderable(obj);
+
+ // OpenGL does not use Java arrays. ByteBuffers are used instead to provide data in a format
+ // that OpenGL understands.
+
+ // Obtain the data from the OBJ, as direct buffers:
+ IntBuffer wideIndices = ObjData.getFaceVertexIndices(obj, 3);
+ FloatBuffer vertices = ObjData.getVertices(obj);
+ FloatBuffer texCoords = ObjData.getTexCoords(obj, 2);
+ FloatBuffer normals = ObjData.getNormals(obj);
+
+ // Convert int indices to shorts for GL ES 2.0 compatibility
+ ShortBuffer indices =
+ ByteBuffer.allocateDirect(2 * wideIndices.limit())
+ .order(ByteOrder.nativeOrder())
+ .asShortBuffer();
+ while (wideIndices.hasRemaining()) {
+ indices.put((short) wideIndices.get());
+ }
+ indices.rewind();
+
+ int[] buffers = new int[2];
+ GLES20.glGenBuffers(2, buffers, 0);
+ vertexBufferId = buffers[0];
+ indexBufferId = buffers[1];
+
+ // Load vertex buffer
+ verticesBaseAddress = 0;
+ texCoordsBaseAddress = verticesBaseAddress + 4 * vertices.limit();
+ normalsBaseAddress = texCoordsBaseAddress + 4 * texCoords.limit();
+ final int totalBytes = normalsBaseAddress + 4 * normals.limit();
+
+ GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vertexBufferId);
+ GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, totalBytes, null, GLES20.GL_STATIC_DRAW);
+ GLES20.glBufferSubData(
+ GLES20.GL_ARRAY_BUFFER, verticesBaseAddress, 4 * vertices.limit(), vertices);
+ GLES20.glBufferSubData(
+ GLES20.GL_ARRAY_BUFFER, texCoordsBaseAddress, 4 * texCoords.limit(), texCoords);
+ GLES20.glBufferSubData(
+ GLES20.GL_ARRAY_BUFFER, normalsBaseAddress, 4 * normals.limit(), normals);
+ GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
+
+ // Load index buffer
+ GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, indexBufferId);
+ indexCount = indices.limit();
+ GLES20.glBufferData(
+ GLES20.GL_ELEMENT_ARRAY_BUFFER, 2 * indexCount, indices, GLES20.GL_STATIC_DRAW);
+ GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0);
+
+ ShaderUtil.checkGLError(TAG, "OBJ buffer load");
+
+ final int vertexShader =
+ ShaderUtil.loadGLShader(TAG, context, GLES20.GL_VERTEX_SHADER, R.raw.object_vertex);
+ final int fragmentShader =
+ ShaderUtil.loadGLShader(TAG, context, GLES20.GL_FRAGMENT_SHADER, R.raw.object_fragment);
+
+ program = GLES20.glCreateProgram();
+ GLES20.glAttachShader(program, vertexShader);
+ GLES20.glAttachShader(program, fragmentShader);
+ GLES20.glLinkProgram(program);
+ GLES20.glUseProgram(program);
+
+ ShaderUtil.checkGLError(TAG, "Program creation");
+
+ modelViewUniform = GLES20.glGetUniformLocation(program, "u_ModelView");
+ modelViewProjectionUniform = GLES20.glGetUniformLocation(program, "u_ModelViewProjection");
+
+ positionAttribute = GLES20.glGetAttribLocation(program, "a_Position");
+ normalAttribute = GLES20.glGetAttribLocation(program, "a_Normal");
+ texCoordAttribute = GLES20.glGetAttribLocation(program, "a_TexCoord");
+
+ textureUniform = GLES20.glGetUniformLocation(program, "u_Texture");
+
+ lightingParametersUniform = GLES20.glGetUniformLocation(program, "u_LightingParameters");
+ materialParametersUniform = GLES20.glGetUniformLocation(program, "u_MaterialParameters");
+
+ ShaderUtil.checkGLError(TAG, "Program parameters");
+
+ Matrix.setIdentityM(modelMatrix, 0);
+ }
+
+ /**
+ * Selects the blending mode for rendering.
+ *
+ * @param blendMode The blending mode. Null indicates no blending (opaque rendering).
+ */
+ public void setBlendMode(BlendMode blendMode) {
+ this.blendMode = blendMode;
+ }
+
+ /**
+ * Updates the object model matrix and applies scaling.
+ *
+ * @param modelMatrix A 4x4 model-to-world transformation matrix, stored in column-major order.
+ * @param scaleFactor A separate scaling factor to apply before the {@code modelMatrix}.
+ * @see Matrix
+ */
+ public void updateModelMatrix(float[] modelMatrix, float scaleFactor) {
+ float[] scaleMatrix = new float[16];
+ Matrix.setIdentityM(scaleMatrix, 0);
+ scaleMatrix[0] = scaleFactor;
+ scaleMatrix[5] = scaleFactor;
+ scaleMatrix[10] = scaleFactor;
+ Matrix.multiplyMM(this.modelMatrix, 0, modelMatrix, 0, scaleMatrix, 0);
+ }
+
+ /**
+ * Sets the surface characteristics of the rendered model.
+ *
+ * @param ambient Intensity of non-directional surface illumination.
+ * @param diffuse Diffuse (matte) surface reflectivity.
+ * @param specular Specular (shiny) surface reflectivity.
+ * @param specularPower Surface shininess. Larger values result in a smaller, sharper specular
+ * highlight.
+ */
+ public void setMaterialProperties(
+ float ambient, float diffuse, float specular, float specularPower) {
+ this.ambient = ambient;
+ this.diffuse = diffuse;
+ this.specular = specular;
+ this.specularPower = specularPower;
+ }
+
+ /**
+ * Draws the model.
+ *
+ * @param cameraView A 4x4 view matrix, in column-major order.
+ * @param cameraPerspective A 4x4 projection matrix, in column-major order.
+ * @param lightIntensity Illumination intensity. Combined with diffuse and specular material
+ * properties.
+ * @see #setBlendMode(BlendMode)
+ * @see #updateModelMatrix(float[], float)
+ * @see #setMaterialProperties(float, float, float, float)
+ * @see Matrix
+ */
+ public void draw(float[] cameraView, float[] cameraPerspective, float lightIntensity) {
+
+ ShaderUtil.checkGLError(TAG, "Before draw");
+
+ // Build the ModelView and ModelViewProjection matrices
+ // for calculating object position and light.
+ Matrix.multiplyMM(modelViewMatrix, 0, cameraView, 0, modelMatrix, 0);
+ Matrix.multiplyMM(modelViewProjectionMatrix, 0, cameraPerspective, 0, modelViewMatrix, 0);
+
+ GLES20.glUseProgram(program);
+
+ // Set the lighting environment properties.
+ Matrix.multiplyMV(viewLightDirection, 0, modelViewMatrix, 0, LIGHT_DIRECTION, 0);
+ normalizeVec3(viewLightDirection);
+ GLES20.glUniform4f(
+ lightingParametersUniform,
+ viewLightDirection[0],
+ viewLightDirection[1],
+ viewLightDirection[2],
+ lightIntensity);
+
+ // Set the object material properties.
+ GLES20.glUniform4f(materialParametersUniform, ambient, diffuse, specular, specularPower);
+
+ // Attach the object texture.
+ GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]);
+ GLES20.glUniform1i(textureUniform, 0);
+
+ // Set the vertex attributes.
+ GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vertexBufferId);
+
+ GLES20.glVertexAttribPointer(
+ positionAttribute, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, 0, verticesBaseAddress);
+ GLES20.glVertexAttribPointer(normalAttribute, 3, GLES20.GL_FLOAT, false, 0, normalsBaseAddress);
+ GLES20.glVertexAttribPointer(
+ texCoordAttribute, 2, GLES20.GL_FLOAT, false, 0, texCoordsBaseAddress);
+
+ GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
+
+ // Set the ModelViewProjection matrix in the shader.
+ GLES20.glUniformMatrix4fv(modelViewUniform, 1, false, modelViewMatrix, 0);
+ GLES20.glUniformMatrix4fv(modelViewProjectionUniform, 1, false, modelViewProjectionMatrix, 0);
+
+ // Enable vertex arrays
+ GLES20.glEnableVertexAttribArray(positionAttribute);
+ GLES20.glEnableVertexAttribArray(normalAttribute);
+ GLES20.glEnableVertexAttribArray(texCoordAttribute);
+
+ if (blendMode != null) {
+ GLES20.glDepthMask(false);
+ GLES20.glEnable(GLES20.GL_BLEND);
+ switch (blendMode) {
+ case Shadow:
+ // Multiplicative blending function for Shadow.
+ GLES20.glBlendFunc(GLES20.GL_ZERO, GLES20.GL_ONE_MINUS_SRC_ALPHA);
+ break;
+ case Grid:
+ // Grid, additive blending function.
+ GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
+ break;
+ }
+ }
+
+ GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, indexBufferId);
+ GLES20.glDrawElements(GLES20.GL_TRIANGLES, indexCount, GLES20.GL_UNSIGNED_SHORT, 0);
+ GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0);
+
+ if (blendMode != null) {
+ GLES20.glDisable(GLES20.GL_BLEND);
+ GLES20.glDepthMask(true);
+ }
+
+ // Disable vertex arrays
+ GLES20.glDisableVertexAttribArray(positionAttribute);
+ GLES20.glDisableVertexAttribArray(normalAttribute);
+ GLES20.glDisableVertexAttribArray(texCoordAttribute);
+
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
+
+ ShaderUtil.checkGLError(TAG, "After draw");
+ }
+
+ private static void normalizeVec3(float[] v) {
+ float reciprocalLength = 1.0f / (float) Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
+ v[0] *= reciprocalLength;
+ v[1] *= reciprocalLength;
+ v[2] *= reciprocalLength;
+ }
+
+ public float[] getModelViewProjectionMatrix(){
+ return modelViewProjectionMatrix;
+ }
+
+ public float[] getModelViewMatrix(){
+ return modelViewMatrix;
+ }
+
+ public float[] getModelMatrix(){
+ return modelMatrix;
+ }
+
+}
diff --git a/app/src/main/java/com/google/ar/core/examples/java/helloar/rendering/PlaneRenderer.java b/app/src/main/java/com/google/ar/core/examples/java/helloar/rendering/PlaneRenderer.java
new file mode 100644
index 0000000..6e825c7
--- /dev/null
+++ b/app/src/main/java/com/google/ar/core/examples/java/helloar/rendering/PlaneRenderer.java
@@ -0,0 +1,429 @@
+
+package com.google.ar.core.examples.java.helloar.rendering;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.opengl.GLES20;
+import android.opengl.GLSurfaceView;
+import android.opengl.GLUtils;
+import android.opengl.Matrix;
+
+import com.google.ar.core.Camera;
+import com.google.ar.core.Plane;
+import com.google.ar.core.Pose;
+import com.google.ar.core.TrackingState;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+import fr.geolabs.dev.mapmint4me.R;
+
+/**
+ * Renders the detected AR planes.
+ */
+public class PlaneRenderer {
+ private static final String TAG = PlaneRenderer.class.getSimpleName();
+
+ private static final int BYTES_PER_FLOAT = Float.SIZE / 8;
+ private static final int BYTES_PER_SHORT = Short.SIZE / 8;
+ private static final int COORDS_PER_VERTEX = 3; // x, z, alpha
+
+ private static final int VERTS_PER_BOUNDARY_VERT = 2;
+ private static final int INDICES_PER_BOUNDARY_VERT = 3;
+ private static final int INITIAL_BUFFER_BOUNDARY_VERTS = 64;
+
+ private static final int INITIAL_VERTEX_BUFFER_SIZE_BYTES =
+ BYTES_PER_FLOAT * COORDS_PER_VERTEX * VERTS_PER_BOUNDARY_VERT * INITIAL_BUFFER_BOUNDARY_VERTS;
+
+ private static final int INITIAL_INDEX_BUFFER_SIZE_BYTES =
+ BYTES_PER_SHORT
+ * INDICES_PER_BOUNDARY_VERT
+ * INDICES_PER_BOUNDARY_VERT
+ * INITIAL_BUFFER_BOUNDARY_VERTS;
+
+ private static final float FADE_RADIUS_M = 0.25f;
+ private static final float DOTS_PER_METER = 10.0f;
+ private static final float EQUILATERAL_TRIANGLE_SCALE = (float) (1 / Math.sqrt(3));
+
+ // Using the "signed distance field" approach to render sharp lines and circles.
+ // {dotThreshold, lineThreshold, lineFadeSpeed, occlusionScale}
+ // dotThreshold/lineThreshold: red/green intensity above which dots/lines are present
+ // lineFadeShrink: lines will fade in between alpha = 1-(1/lineFadeShrink) and 1.0
+ // occlusionShrink: occluded planes will fade out between alpha = 0 and 1/occlusionShrink
+ private static final float[] GRID_CONTROL = {0.2f, 0.4f, 2.0f, 1.5f};
+
+ private int planeProgram;
+ private final int[] textures = new int[1];
+
+ private int planeXZPositionAlphaAttribute;
+
+ private int planeModelUniform;
+ private int planeModelViewProjectionUniform;
+ private int textureUniform;
+ private int lineColorUniform;
+ private int dotColorUniform;
+ private int gridControlUniform;
+ private int planeUvMatrixUniform;
+
+ private FloatBuffer vertexBuffer =
+ ByteBuffer.allocateDirect(INITIAL_VERTEX_BUFFER_SIZE_BYTES)
+ .order(ByteOrder.nativeOrder())
+ .asFloatBuffer();
+ private ShortBuffer indexBuffer =
+ ByteBuffer.allocateDirect(INITIAL_INDEX_BUFFER_SIZE_BYTES)
+ .order(ByteOrder.nativeOrder())
+ .asShortBuffer();
+
+ // Temporary lists/matrices allocated here to reduce number of allocations for each frame.
+ private final float[] modelMatrix = new float[16];
+ private final float[] modelViewMatrix = new float[16];
+ private final float[] modelViewProjectionMatrix = new float[16];
+ private final float[] planeColor = new float[4];
+ private final float[] planeAngleUvMatrix =
+ new float[4]; // 2x2 rotation matrix applied to uv coords.
+
+ private final Map planeIndexMap = new HashMap<>();
+
+ public PlaneRenderer() {
+ }
+
+ /**
+ * Allocates and initializes OpenGL resources needed by the plane renderer. Must be called on the
+ * OpenGL thread, typically in {@link GLSurfaceView.Renderer#onSurfaceCreated(GL10, EGLConfig)}.
+ *
+ * @param context Needed to access shader source and texture PNG.
+ * @param gridDistanceTextureName Name of the PNG file containing the grid texture.
+ */
+ public void createOnGlThread(Context context, String gridDistanceTextureName) throws IOException {
+ int vertexShader =
+ ShaderUtil.loadGLShader(TAG, context, GLES20.GL_VERTEX_SHADER, R.raw.plane_vertex);
+ int passthroughShader =
+ ShaderUtil.loadGLShader(TAG, context, GLES20.GL_FRAGMENT_SHADER, R.raw.plane_fragment);
+
+ planeProgram = GLES20.glCreateProgram();
+ GLES20.glAttachShader(planeProgram, vertexShader);
+ GLES20.glAttachShader(planeProgram, passthroughShader);
+ GLES20.glLinkProgram(planeProgram);
+ GLES20.glUseProgram(planeProgram);
+
+ ShaderUtil.checkGLError(TAG, "Program creation");
+
+ // Read the texture.
+ Bitmap textureBitmap =
+ BitmapFactory.decodeStream(context.getAssets().open(gridDistanceTextureName));
+
+ GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+ GLES20.glGenTextures(textures.length, textures, 0);
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]);
+
+ GLES20.glTexParameteri(
+ GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR_MIPMAP_LINEAR);
+ GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
+ GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, textureBitmap, 0);
+ GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D);
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
+
+ ShaderUtil.checkGLError(TAG, "Texture loading");
+
+ planeXZPositionAlphaAttribute = GLES20.glGetAttribLocation(planeProgram, "a_XZPositionAlpha");
+
+ planeModelUniform = GLES20.glGetUniformLocation(planeProgram, "u_Model");
+ planeModelViewProjectionUniform =
+ GLES20.glGetUniformLocation(planeProgram, "u_ModelViewProjection");
+ textureUniform = GLES20.glGetUniformLocation(planeProgram, "u_Texture");
+ lineColorUniform = GLES20.glGetUniformLocation(planeProgram, "u_lineColor");
+ dotColorUniform = GLES20.glGetUniformLocation(planeProgram, "u_dotColor");
+ gridControlUniform = GLES20.glGetUniformLocation(planeProgram, "u_gridControl");
+ planeUvMatrixUniform = GLES20.glGetUniformLocation(planeProgram, "u_PlaneUvMatrix");
+
+ ShaderUtil.checkGLError(TAG, "Program parameters");
+ }
+
+ /**
+ * Updates the plane model transform matrix and extents.
+ */
+ private void updatePlaneParameters(
+ float[] planeMatrix, float extentX, float extentZ, FloatBuffer boundary) {
+ System.arraycopy(planeMatrix, 0, modelMatrix, 0, 16);
+ if (boundary == null) {
+ vertexBuffer.limit(0);
+ indexBuffer.limit(0);
+ return;
+ }
+
+ // Generate a new set of vertices and a corresponding triangle strip index set so that
+ // the plane boundary polygon has a fading edge. This is done by making a copy of the
+ // boundary polygon vertices and scaling it down around center to push it inwards. Then
+ // the index buffer is setup accordingly.
+ boundary.rewind();
+ int boundaryVertices = boundary.limit() / 2;
+ int numVertices;
+ int numIndices;
+
+ numVertices = boundaryVertices * VERTS_PER_BOUNDARY_VERT;
+ // drawn as GL_TRIANGLE_STRIP with 3n-2 triangles (n-2 for fill, 2n for perimeter).
+ numIndices = boundaryVertices * INDICES_PER_BOUNDARY_VERT;
+
+ if (vertexBuffer.capacity() < numVertices * COORDS_PER_VERTEX) {
+ int size = vertexBuffer.capacity();
+ while (size < numVertices * COORDS_PER_VERTEX) {
+ size *= 2;
+ }
+ vertexBuffer =
+ ByteBuffer.allocateDirect(BYTES_PER_FLOAT * size)
+ .order(ByteOrder.nativeOrder())
+ .asFloatBuffer();
+ }
+ vertexBuffer.rewind();
+ vertexBuffer.limit(numVertices * COORDS_PER_VERTEX);
+
+ if (indexBuffer.capacity() < numIndices) {
+ int size = indexBuffer.capacity();
+ while (size < numIndices) {
+ size *= 2;
+ }
+ indexBuffer =
+ ByteBuffer.allocateDirect(BYTES_PER_SHORT * size)
+ .order(ByteOrder.nativeOrder())
+ .asShortBuffer();
+ }
+ indexBuffer.rewind();
+ indexBuffer.limit(numIndices);
+
+ // Note: when either dimension of the bounding box is smaller than 2*FADE_RADIUS_M we
+ // generate a bunch of 0-area triangles. These don't get rendered though so it works
+ // out ok.
+ float xScale = Math.max((extentX - 2 * FADE_RADIUS_M) / extentX, 0.0f);
+ float zScale = Math.max((extentZ - 2 * FADE_RADIUS_M) / extentZ, 0.0f);
+
+ while (boundary.hasRemaining()) {
+ float x = boundary.get();
+ float z = boundary.get();
+ vertexBuffer.put(x);
+ vertexBuffer.put(z);
+ vertexBuffer.put(0.0f);
+ vertexBuffer.put(x * xScale);
+ vertexBuffer.put(z * zScale);
+ vertexBuffer.put(1.0f);
+ }
+
+ // step 1, perimeter
+ indexBuffer.put((short) ((boundaryVertices - 1) * 2));
+ for (int i = 0; i < boundaryVertices; ++i) {
+ indexBuffer.put((short) (i * 2));
+ indexBuffer.put((short) (i * 2 + 1));
+ }
+ indexBuffer.put((short) 1);
+ // This leaves us on the interior edge of the perimeter between the inset vertices
+ // for boundary verts n-1 and 0.
+
+ // step 2, interior:
+ for (int i = 1; i < boundaryVertices / 2; ++i) {
+ indexBuffer.put((short) ((boundaryVertices - 1 - i) * 2 + 1));
+ indexBuffer.put((short) (i * 2 + 1));
+ }
+ if (boundaryVertices % 2 != 0) {
+ indexBuffer.put((short) ((boundaryVertices / 2) * 2 + 1));
+ }
+ }
+
+ private void draw(float[] cameraView, float[] cameraPerspective) {
+ // Build the ModelView and ModelViewProjection matrices
+ // for calculating cube position and light.
+ Matrix.multiplyMM(modelViewMatrix, 0, cameraView, 0, modelMatrix, 0);
+ Matrix.multiplyMM(modelViewProjectionMatrix, 0, cameraPerspective, 0, modelViewMatrix, 0);
+
+ // Set the position of the plane
+ vertexBuffer.rewind();
+ GLES20.glVertexAttribPointer(
+ planeXZPositionAlphaAttribute,
+ COORDS_PER_VERTEX,
+ GLES20.GL_FLOAT,
+ false,
+ BYTES_PER_FLOAT * COORDS_PER_VERTEX,
+ vertexBuffer);
+
+ // Set the Model and ModelViewProjection matrices in the shader.
+ GLES20.glUniformMatrix4fv(planeModelUniform, 1, false, modelMatrix, 0);
+ GLES20.glUniformMatrix4fv(
+ planeModelViewProjectionUniform, 1, false, modelViewProjectionMatrix, 0);
+
+ indexBuffer.rewind();
+ GLES20.glDrawElements(
+ GLES20.GL_TRIANGLE_STRIP, indexBuffer.limit(), GLES20.GL_UNSIGNED_SHORT, indexBuffer);
+ ShaderUtil.checkGLError(TAG, "Drawing plane");
+ }
+
+ static class SortablePlane {
+ final float distance;
+ final Plane plane;
+
+ SortablePlane(float distance, Plane plane) {
+ this.distance = distance;
+ this.plane = plane;
+ }
+ }
+
+ /**
+ * Draws the collection of tracked planes, with closer planes hiding more distant ones.
+ *
+ * @param allPlanes The collection of planes to draw.
+ * @param cameraPose The pose of the camera, as returned by {@link Camera#getPose()}
+ * @param cameraPerspective The projection matrix, as returned by {@link
+ * Camera#getProjectionMatrix(float[], int, float, float)}
+ */
+ public void drawPlanes(Collection allPlanes, Pose cameraPose, float[] cameraPerspective) {
+ // Planes must be sorted by distance from camera so that we draw closer planes first, and
+ // they occlude the farther planes.
+ List sortedPlanes = new ArrayList<>();
+ float[] normal = new float[3];
+ float cameraX = cameraPose.tx();
+ float cameraY = cameraPose.ty();
+ float cameraZ = cameraPose.tz();
+ for (Plane plane : allPlanes) {
+ if (plane.getTrackingState() != TrackingState.TRACKING || plane.getSubsumedBy() != null) {
+ continue;
+ }
+
+ Pose center = plane.getCenterPose();
+ // Get transformed Y axis of plane's coordinate system.
+ center.getTransformedAxis(1, 1.0f, normal, 0);
+ // Compute dot product of plane's normal with vector from camera to plane center.
+ float distance =
+ (cameraX - center.tx()) * normal[0]
+ + (cameraY - center.ty()) * normal[1]
+ + (cameraZ - center.tz()) * normal[2];
+ if (distance < 0) { // Plane is back-facing.
+ continue;
+ }
+ sortedPlanes.add(new SortablePlane(distance, plane));
+ }
+ Collections.sort(
+ sortedPlanes,
+ new Comparator() {
+ @Override
+ public int compare(SortablePlane a, SortablePlane b) {
+ return Float.compare(a.distance, b.distance);
+ }
+ });
+
+ float[] cameraView = new float[16];
+ cameraPose.inverse().toMatrix(cameraView, 0);
+
+ // Planes are drawn with additive blending, masked by the alpha channel for occlusion.
+
+ // Start by clearing the alpha channel of the color buffer to 1.0.
+ GLES20.glClearColor(1, 1, 1, 1);
+ GLES20.glColorMask(false, false, false, true);
+ GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+ GLES20.glColorMask(true, true, true, true);
+
+ // Disable depth write.
+ GLES20.glDepthMask(false);
+
+ // Additive blending, masked by alpha channel, clearing alpha channel.
+ GLES20.glEnable(GLES20.GL_BLEND);
+ GLES20.glBlendFuncSeparate(
+ GLES20.GL_DST_ALPHA, GLES20.GL_ONE, // RGB (src, dest)
+ GLES20.GL_ZERO, GLES20.GL_ONE_MINUS_SRC_ALPHA); // ALPHA (src, dest)
+
+ // Set up the shader.
+ GLES20.glUseProgram(planeProgram);
+
+ // Attach the texture.
+ GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]);
+ GLES20.glUniform1i(textureUniform, 0);
+
+ // Shared fragment uniforms.
+ GLES20.glUniform4fv(gridControlUniform, 1, GRID_CONTROL, 0);
+
+ // Enable vertex arrays
+ GLES20.glEnableVertexAttribArray(planeXZPositionAlphaAttribute);
+
+ ShaderUtil.checkGLError(TAG, "Setting up to draw planes");
+
+ for (SortablePlane sortedPlane : sortedPlanes) {
+ Plane plane = sortedPlane.plane;
+ float[] planeMatrix = new float[16];
+ plane.getCenterPose().toMatrix(planeMatrix, 0);
+
+ updatePlaneParameters(
+ planeMatrix, plane.getExtentX(), plane.getExtentZ(), plane.getPolygon());
+
+ // Get plane index. Keep a map to assign same indices to same planes.
+ Integer planeIndex = planeIndexMap.get(plane);
+ if (planeIndex == null) {
+ planeIndex = planeIndexMap.size();
+ planeIndexMap.put(plane, planeIndex);
+ }
+
+ // Set plane color. Computed deterministically from the Plane index.
+ int colorIndex = planeIndex % PLANE_COLORS_RGBA.length;
+ colorRgbaToFloat(planeColor, PLANE_COLORS_RGBA[colorIndex]);
+ GLES20.glUniform4fv(lineColorUniform, 1, planeColor, 0);
+ GLES20.glUniform4fv(dotColorUniform, 1, planeColor, 0);
+
+ // Each plane will have its own angle offset from others, to make them easier to
+ // distinguish. Compute a 2x2 rotation matrix from the angle.
+ float angleRadians = planeIndex * 0.144f;
+ float uScale = DOTS_PER_METER;
+ float vScale = DOTS_PER_METER * EQUILATERAL_TRIANGLE_SCALE;
+ planeAngleUvMatrix[0] = +(float) Math.cos(angleRadians) * uScale;
+ planeAngleUvMatrix[1] = -(float) Math.sin(angleRadians) * vScale;
+ planeAngleUvMatrix[2] = +(float) Math.sin(angleRadians) * uScale;
+ planeAngleUvMatrix[3] = +(float) Math.cos(angleRadians) * vScale;
+ GLES20.glUniformMatrix2fv(planeUvMatrixUniform, 1, false, planeAngleUvMatrix, 0);
+
+ draw(cameraView, cameraPerspective);
+ }
+
+ // Clean up the state we set
+ GLES20.glDisableVertexAttribArray(planeXZPositionAlphaAttribute);
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
+ GLES20.glDisable(GLES20.GL_BLEND);
+ GLES20.glDepthMask(true);
+
+ ShaderUtil.checkGLError(TAG, "Cleaning up after drawing planes");
+ }
+
+ private static void colorRgbaToFloat(float[] planeColor, int colorRgba) {
+ planeColor[0] = ((float) ((colorRgba >> 24) & 0xff)) / 255.0f;
+ planeColor[1] = ((float) ((colorRgba >> 16) & 0xff)) / 255.0f;
+ planeColor[2] = ((float) ((colorRgba >> 8) & 0xff)) / 255.0f;
+ planeColor[3] = ((float) ((colorRgba >> 0) & 0xff)) / 255.0f;
+ }
+
+ private static final int[] PLANE_COLORS_RGBA = {
+ 0xFFFFFFFF,
+ 0xF44336FF,
+ 0xE91E63FF,
+ 0x9C27B0FF,
+ 0x673AB7FF,
+ 0x3F51B5FF,
+ 0x2196F3FF,
+ 0x03A9F4FF,
+ 0x00BCD4FF,
+ 0x009688FF,
+ 0x4CAF50FF,
+ 0x8BC34AFF,
+ 0xCDDC39FF,
+ 0xFFEB3BFF,
+ 0xFFC107FF,
+ 0xFF9800FF,
+ };
+}
diff --git a/app/src/main/java/com/google/ar/core/examples/java/helloar/rendering/PointCloudRenderer.java b/app/src/main/java/com/google/ar/core/examples/java/helloar/rendering/PointCloudRenderer.java
new file mode 100644
index 0000000..2b96b15
--- /dev/null
+++ b/app/src/main/java/com/google/ar/core/examples/java/helloar/rendering/PointCloudRenderer.java
@@ -0,0 +1,144 @@
+
+package com.google.ar.core.examples.java.helloar.rendering;
+
+import android.content.Context;
+import android.opengl.GLES20;
+import android.opengl.GLSurfaceView;
+import android.opengl.Matrix;
+
+import com.google.ar.core.PointCloud;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+import fr.geolabs.dev.mapmint4me.R;
+
+/**
+ * Renders a point cloud.
+ */
+public class PointCloudRenderer {
+ private static final String TAG = PointCloud.class.getSimpleName();
+
+ private static final int BYTES_PER_FLOAT = Float.SIZE / 8;
+ private static final int FLOATS_PER_POINT = 4; // X,Y,Z,confidence.
+ private static final int BYTES_PER_POINT = BYTES_PER_FLOAT * FLOATS_PER_POINT;
+ private static final int INITIAL_BUFFER_POINTS = 1000;
+
+ private int vbo;
+ private int vboSize;
+
+ private int programName;
+ private int positionAttribute;
+ private int modelViewProjectionUniform;
+ private int colorUniform;
+ private int pointSizeUniform;
+
+ private int numPoints = 0;
+
+ // Keep track of the last point cloud rendered to avoid updating the VBO if point cloud
+ // was not changed.
+ private PointCloud lastPointCloud = null;
+
+ public PointCloudRenderer() {}
+
+ /**
+ * Allocates and initializes OpenGL resources needed by the plane renderer. Must be called on the
+ * OpenGL thread, typically in {@link GLSurfaceView.Renderer#onSurfaceCreated(GL10, EGLConfig)}.
+ *
+ * @param context Needed to access shader source.
+ */
+ public void createOnGlThread(Context context) {
+ ShaderUtil.checkGLError(TAG, "before create");
+
+ int[] buffers = new int[1];
+ GLES20.glGenBuffers(1, buffers, 0);
+ vbo = buffers[0];
+ GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vbo);
+
+ vboSize = INITIAL_BUFFER_POINTS * BYTES_PER_POINT;
+ GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, vboSize, null, GLES20.GL_DYNAMIC_DRAW);
+ GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
+
+ ShaderUtil.checkGLError(TAG, "buffer alloc");
+
+ int vertexShader =
+ ShaderUtil.loadGLShader(TAG, context, GLES20.GL_VERTEX_SHADER, R.raw.point_cloud_vertex);
+ int passthroughShader =
+ ShaderUtil.loadGLShader(
+ TAG, context, GLES20.GL_FRAGMENT_SHADER, R.raw.passthrough_fragment);
+
+ programName = GLES20.glCreateProgram();
+ GLES20.glAttachShader(programName, vertexShader);
+ GLES20.glAttachShader(programName, passthroughShader);
+ GLES20.glLinkProgram(programName);
+ GLES20.glUseProgram(programName);
+
+ ShaderUtil.checkGLError(TAG, "program");
+
+ positionAttribute = GLES20.glGetAttribLocation(programName, "a_Position");
+ colorUniform = GLES20.glGetUniformLocation(programName, "u_Color");
+ modelViewProjectionUniform = GLES20.glGetUniformLocation(programName, "u_ModelViewProjection");
+ pointSizeUniform = GLES20.glGetUniformLocation(programName, "u_PointSize");
+
+ ShaderUtil.checkGLError(TAG, "program params");
+ }
+
+ /**
+ * Updates the OpenGL buffer contents to the provided point. Repeated calls with the same point
+ * cloud will be ignored.
+ */
+ public void update(PointCloud cloud) {
+ if (lastPointCloud == cloud) {
+ // Redundant call.
+ return;
+ }
+
+ ShaderUtil.checkGLError(TAG, "before update");
+
+ GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vbo);
+ lastPointCloud = cloud;
+
+ // If the VBO is not large enough to fit the new point cloud, resize it.
+ numPoints = lastPointCloud.getPoints().remaining() / FLOATS_PER_POINT;
+ if (numPoints * BYTES_PER_POINT > vboSize) {
+ while (numPoints * BYTES_PER_POINT > vboSize) {
+ vboSize *= 2;
+ }
+ GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, vboSize, null, GLES20.GL_DYNAMIC_DRAW);
+ }
+ GLES20.glBufferSubData(
+ GLES20.GL_ARRAY_BUFFER, 0, numPoints * BYTES_PER_POINT, lastPointCloud.getPoints());
+ GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
+
+ ShaderUtil.checkGLError(TAG, "after update");
+ }
+
+ /**
+ * Renders the point cloud. ArCore point cloud is given in world space.
+ *
+ * @param cameraView the camera view matrix for this frame, typically from {@link
+ * com.google.ar.core.Camera#getViewMatrix(float[], int)}.
+ * @param cameraPerspective the camera projection matrix for this frame, typically from {@link
+ * com.google.ar.core.Camera#getProjectionMatrix(float[], int, float, float)}.
+ */
+ public void draw(float[] cameraView, float[] cameraPerspective) {
+ float[] modelViewProjection = new float[16];
+ Matrix.multiplyMM(modelViewProjection, 0, cameraPerspective, 0, cameraView, 0);
+
+ ShaderUtil.checkGLError(TAG, "Before draw");
+
+ GLES20.glUseProgram(programName);
+ GLES20.glEnableVertexAttribArray(positionAttribute);
+ GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vbo);
+ GLES20.glVertexAttribPointer(positionAttribute, 4, GLES20.GL_FLOAT, false, BYTES_PER_POINT, 0);
+ GLES20.glUniform4f(colorUniform, 31.0f / 255.0f, 188.0f / 255.0f, 210.0f / 255.0f, 1.0f);
+ GLES20.glUniformMatrix4fv(modelViewProjectionUniform, 1, false, modelViewProjection, 0);
+ GLES20.glUniform1f(pointSizeUniform, 5.0f);
+
+ GLES20.glDrawArrays(GLES20.GL_POINTS, 0, numPoints);
+ GLES20.glDisableVertexAttribArray(positionAttribute);
+ GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
+
+ ShaderUtil.checkGLError(TAG, "Draw");
+ }
+}
diff --git a/app/src/main/java/com/google/ar/core/examples/java/helloar/rendering/ShaderUtil.java b/app/src/main/java/com/google/ar/core/examples/java/helloar/rendering/ShaderUtil.java
new file mode 100755
index 0000000..93dc727
--- /dev/null
+++ b/app/src/main/java/com/google/ar/core/examples/java/helloar/rendering/ShaderUtil.java
@@ -0,0 +1,89 @@
+
+package com.google.ar.core.examples.java.helloar.rendering;
+
+import android.content.Context;
+import android.opengl.GLES20;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+/**
+ * Shader helper functions.
+ */
+public class ShaderUtil {
+ /**
+ * Converts a raw text file, saved as a resource, into an OpenGL ES shader.
+ *
+ * @param type The type of shader we will be creating.
+ * @param resId The resource ID of the raw text file about to be turned into a shader.
+ * @return The shader object handler.
+ */
+ public static int loadGLShader(String tag, Context context, int type, int resId) {
+ String code = readRawTextFile(context, resId);
+ int shader = GLES20.glCreateShader(type);
+ GLES20.glShaderSource(shader, code);
+ GLES20.glCompileShader(shader);
+
+ // Get the compilation status.
+ final int[] compileStatus = new int[1];
+ GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compileStatus, 0);
+
+ // If the compilation failed, delete the shader.
+ if (compileStatus[0] == 0) {
+ Log.e(tag, "Error compiling shader: " + GLES20.glGetShaderInfoLog(shader));
+ GLES20.glDeleteShader(shader);
+ shader = 0;
+ }
+
+ if (shader == 0) {
+ throw new RuntimeException("Error creating shader.");
+ }
+
+ return shader;
+ }
+
+ /**
+ * Checks if we've had an error inside of OpenGL ES, and if so what that error is.
+ *
+ * @param label Label to report in case of error.
+ * @throws RuntimeException If an OpenGL error is detected.
+ */
+ public static void checkGLError(String tag, String label) {
+ int lastError = GLES20.GL_NO_ERROR;
+ // Drain the queue of all errors.
+ int error;
+ while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
+ Log.e(tag, label + ": glError " + error);
+ lastError = error;
+ }
+ if (lastError != GLES20.GL_NO_ERROR) {
+ throw new RuntimeException(label + ": glError " + lastError);
+ }
+ }
+
+ /**
+ * Converts a raw text file into a string.
+ *
+ * @param resId The resource ID of the raw text file about to be turned into a shader.
+ * @return The context of the text file, or null in case of error.
+ */
+ private static String readRawTextFile(Context context, int resId) {
+ InputStream inputStream = context.getResources().openRawResource(resId);
+ try {
+ BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
+ StringBuilder sb = new StringBuilder();
+ String line;
+ while ((line = reader.readLine()) != null) {
+ sb.append(line).append("\n");
+ }
+ reader.close();
+ return sb.toString();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+}
diff --git a/app/src/main/java/com/hl3hl3/arcoremeasure/ArMeasureActivity.java b/app/src/main/java/com/hl3hl3/arcoremeasure/ArMeasureActivity.java
new file mode 100644
index 0000000..0bcc394
--- /dev/null
+++ b/app/src/main/java/com/hl3hl3/arcoremeasure/ArMeasureActivity.java
@@ -0,0 +1,967 @@
+package com.hl3hl3.arcoremeasure;
+
+import android.content.Context;
+import android.opengl.GLES20;
+import android.opengl.GLSurfaceView;
+import android.opengl.Matrix;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.design.widget.BaseTransientBottomBar;
+import android.support.design.widget.FloatingActionButton;
+import android.support.design.widget.Snackbar;
+import android.support.v7.app.AppCompatActivity;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.PopupWindow;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.crashlytics.android.BuildConfig;
+import com.crashlytics.android.Crashlytics;
+import com.google.ar.core.Anchor;
+import com.google.ar.core.ArCoreApk;
+import com.google.ar.core.Camera;
+import com.google.ar.core.Config;
+import com.google.ar.core.Frame;
+import com.google.ar.core.HitResult;
+import com.google.ar.core.Plane;
+import com.google.ar.core.Point;
+import com.google.ar.core.PointCloud;
+import com.google.ar.core.Pose;
+import com.google.ar.core.Session;
+import com.google.ar.core.Trackable;
+import com.google.ar.core.TrackingState;
+import com.google.ar.core.examples.java.helloar.CameraPermissionHelper;
+import com.google.ar.core.examples.java.helloar.DisplayRotationHelper;
+import com.google.ar.core.examples.java.helloar.rendering.BackgroundRenderer;
+import com.google.ar.core.examples.java.helloar.rendering.ObjectRenderer;
+import com.google.ar.core.examples.java.helloar.rendering.PlaneRenderer;
+import com.google.ar.core.examples.java.helloar.rendering.PointCloudRenderer;
+import com.google.ar.core.exceptions.CameraNotAvailableException;
+import com.google.ar.core.exceptions.NotTrackingException;
+import com.google.ar.core.exceptions.UnavailableApkTooOldException;
+import com.google.ar.core.exceptions.UnavailableArcoreNotInstalledException;
+import com.google.ar.core.exceptions.UnavailableSdkTooOldException;
+import com.google.ar.core.exceptions.UnavailableUserDeclinedInstallationException;
+import com.hl3hl3.arcoremeasure.renderer.RectanglePolygonRenderer;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.concurrent.ArrayBlockingQueue;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+import fr.geolabs.dev.mapmint4me.R;
+import io.fabric.sdk.android.Fabric;
+
+
+public class ArMeasureActivity extends AppCompatActivity {
+ private static final String TAG = ArMeasureActivity.class.getSimpleName();
+ private static final String ASSET_NAME_CUBE_OBJ = "cube.obj";
+ private static final String ASSET_NAME_CUBE = "cube_green.png";
+ private static final String ASSET_NAME_CUBE_SELECTED = "cube_cyan.png";
+
+ private static final int MAX_CUBE_COUNT = 16;
+
+ // Rendering. The Renderers are created here, and initialized when the GL surface is created.
+ private GLSurfaceView surfaceView = null;
+
+ private boolean installRequested;
+
+ private Session session = null;
+ private GestureDetector gestureDetector;
+ private Snackbar messageSnackbar = null;
+ private DisplayRotationHelper displayRotationHelper;
+
+ private final BackgroundRenderer backgroundRenderer = new BackgroundRenderer();
+ private final PlaneRenderer planeRenderer = new PlaneRenderer();
+ private final PointCloudRenderer pointCloud = new PointCloudRenderer();
+
+ private final ObjectRenderer cube = new ObjectRenderer();
+ private final ObjectRenderer cubeSelected = new ObjectRenderer();
+ private RectanglePolygonRenderer rectRenderer = null;
+
+ // Temporary matrix allocated here to reduce number of allocations for each frame.
+ private final float[] anchorMatrix = new float[MAX_CUBE_COUNT];
+ private final ImageView[] ivCubeIconList = new ImageView[MAX_CUBE_COUNT];
+ private final int[] cubeIconIdArray = {
+ R.id.iv_cube1,
+ R.id.iv_cube2,
+ R.id.iv_cube3,
+ R.id.iv_cube4,
+ R.id.iv_cube5,
+ R.id.iv_cube6,
+ R.id.iv_cube7,
+ R.id.iv_cube8,
+ R.id.iv_cube9,
+ R.id.iv_cube10,
+ R.id.iv_cube11,
+ R.id.iv_cube12,
+ R.id.iv_cube13,
+ R.id.iv_cube14,
+ R.id.iv_cube15,
+ R.id.iv_cube16
+ };
+
+ // Tap handling and UI.
+ private ArrayBlockingQueue queuedSingleTaps = new ArrayBlockingQueue<>(MAX_CUBE_COUNT);
+ private ArrayBlockingQueue queuedLongPress = new ArrayBlockingQueue<>(MAX_CUBE_COUNT);
+ private final ArrayList anchors = new ArrayList<>();
+ private ArrayList showingTapPointX = new ArrayList<>();
+ private ArrayList showingTapPointY = new ArrayList<>();
+
+ private ArrayBlockingQueue queuedScrollDx = new ArrayBlockingQueue<>(MAX_CUBE_COUNT);
+ private ArrayBlockingQueue queuedScrollDy = new ArrayBlockingQueue<>(MAX_CUBE_COUNT);
+
+ private void log(String tag, String log){
+ if(BuildConfig.DEBUG) {
+ Log.d(tag, log);
+ }
+ }
+
+ private void log(Exception e){
+ try {
+ Crashlytics.logException(e);
+ if (BuildConfig.DEBUG) {
+ e.printStackTrace();
+ }
+ }catch (Exception ex){
+ if (BuildConfig.DEBUG) {
+ ex.printStackTrace();
+ }
+ }
+ }
+
+ private void logStatus(String msg){
+ try {
+ Crashlytics.log(msg);
+ }catch (Exception e){
+ log(e);
+ }
+ }
+
+ // OverlayView overlayViewForTest;
+ private TextView tv_result;
+ private FloatingActionButton fab;
+
+ private GLSurfaceRenderer glSerfaceRenderer = null;
+ private GestureDetector.SimpleOnGestureListener gestureDetectorListener = new GestureDetector.SimpleOnGestureListener() {
+ @Override
+ public boolean onSingleTapUp(MotionEvent e) {
+ // Queue tap if there is space. Tap is lost if queue is full.
+ queuedSingleTaps.offer(e);
+// log(TAG, "onSingleTapUp, e=" + e.getRawX() + ", " + e.getRawY());
+ return true;
+ }
+
+ @Override
+ public void onLongPress(MotionEvent e) {
+ queuedLongPress.offer(e);
+ }
+
+ @Override
+ public boolean onDown(MotionEvent e) {
+ return true;
+ }
+
+ @Override
+ public boolean onScroll(MotionEvent e1, MotionEvent e2,
+ float distanceX, float distanceY) {
+// log(TAG, "onScroll, dx=" + distanceX + " dy=" + distanceY);
+ queuedScrollDx.offer(distanceX);
+ queuedScrollDy.offer(distanceY);
+ return true;
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Fabric.with(this, new Crashlytics());
+ setContentView(R.layout.activity_main);
+
+// overlayViewForTest = (OverlayView)findViewById(R.id.overlay_for_test);
+ tv_result = findViewById(R.id.tv_result);
+ fab = findViewById(R.id.fab);
+
+ for(int i=0; i= Build.VERSION_CODES.N) {
+ float screenWidth = getResources().getDisplayMetrics().widthPixels;
+ float screenHeight = getResources().getDisplayMetrics().heightPixels;
+ popUp.showAtLocation(v, Gravity.NO_GRAVITY, (int)screenWidth/2, (int)screenHeight/2);
+ } else {
+ popUp.showAsDropDown(v);
+ }
+ }
+ });
+ fab.hide();
+
+
+ displayRotationHelper = new DisplayRotationHelper(/*context=*/ this);
+
+ if(CameraPermissionHelper.hasCameraPermission(this)){
+ setupRenderer();
+ }
+
+ installRequested = false;
+ }
+
+ private void setupRenderer(){
+ if(surfaceView != null){
+ return;
+ }
+ surfaceView = findViewById(R.id.surfaceview);
+
+ // Set up tap listener.
+ gestureDetector = new GestureDetector(this, gestureDetectorListener);
+ surfaceView.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ return gestureDetector.onTouchEvent(event);
+ }
+ });
+ glSerfaceRenderer = new GLSurfaceRenderer(this);
+ surfaceView.setPreserveEGLContextOnPause(true);
+ surfaceView.setEGLContextClientVersion(2);
+ surfaceView.setEGLConfigChooser(8, 8, 8, 8, 16, 0); // Alpha used for plane blending.
+ surfaceView.setRenderer(glSerfaceRenderer);
+ surfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ logStatus("onResume()");
+ if (session == null) {
+ Exception exception = null;
+ String message = null;
+ try {
+ switch (ArCoreApk.getInstance().requestInstall(this, !installRequested)) {
+ case INSTALL_REQUESTED:
+ installRequested = true;
+ return;
+ case INSTALLED:
+ break;
+ }
+
+ // ARCore requires camera permissions to operate. If we did not yet obtain runtime
+ // permission on Android M and above, now is a good time to ask the user for it.
+ if (!CameraPermissionHelper.hasCameraPermission(this)) {
+ CameraPermissionHelper.requestCameraPermission(this);
+ return;
+ }
+
+ session = new Session(/* context= */ this);
+ } catch (UnavailableArcoreNotInstalledException
+ | UnavailableUserDeclinedInstallationException e) {
+ message = "Please install ARCore";
+ exception = e;
+ } catch (UnavailableApkTooOldException e) {
+ message = "Please update ARCore";
+ exception = e;
+ } catch (UnavailableSdkTooOldException e) {
+ message = "Please update this app";
+ exception = e;
+ } catch (Exception e) {
+ message = "This device does not support AR";
+ exception = e;
+ }
+
+ if (message != null) {
+ showSnackbarMessage(message, true);
+ Log.e(TAG, "Exception creating session", exception);
+ return;
+ }
+
+ // Create default config and check if supported.
+ Config config = new Config(session);
+ if (!session.isSupported(config)) {
+ showSnackbarMessage("This device does not support AR", true);
+ }
+ session.configure(config);
+
+ setupRenderer();
+ }
+
+ showLoadingMessage();
+ // Note that order matters - see the note in onPause(), the reverse applies here.
+ try {
+ session.resume();
+ } catch (CameraNotAvailableException e) {
+ e.printStackTrace();
+ }
+ surfaceView.onResume();
+ displayRotationHelper.onResume();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ logStatus("onPause()");
+ if (session != null) {
+ // Note that the order matters - GLSurfaceView is paused first so that it does not try
+ // to query the session. If Session is paused before GLSurfaceView, GLSurfaceView may
+ // still call session.update() and get a SessionPausedException.
+ displayRotationHelper.onPause();
+ surfaceView.onPause();
+ session.pause();
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] results) {
+ logStatus("onRequestPermissionsResult()");
+ Toast.makeText(this, R.string.need_permission, Toast.LENGTH_LONG)
+ .show();
+ if (!CameraPermissionHelper.shouldShowRequestPermissionRationale(this)) {
+ // Permission denied with checking "Do not ask again".
+ CameraPermissionHelper.launchPermissionSettings(this);
+ }
+ finish();
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ super.onWindowFocusChanged(hasFocus);
+ logStatus("onWindowFocusChanged()");
+ if (hasFocus) {
+ // Standard Android full-screen functionality.
+ getWindow().getDecorView().setSystemUiVisibility(
+ View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+ }
+
+ private void showLoadingMessage() {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ messageSnackbar = Snackbar.make(
+ ArMeasureActivity.this.findViewById(android.R.id.content),
+ "Searching for surfaces...", Snackbar.LENGTH_INDEFINITE);
+ messageSnackbar.getView().setBackgroundColor(0xbf323232);
+ messageSnackbar.show();
+ }
+ });
+ }
+
+ private void hideLoadingMessage() {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if(messageSnackbar != null) {
+ messageSnackbar.dismiss();
+ }
+ messageSnackbar = null;
+ }
+ });
+ }
+
+ private void showSnackbarMessage(String message, boolean finishOnDismiss) {
+ messageSnackbar =
+ Snackbar.make(
+ ArMeasureActivity.this.findViewById(android.R.id.content),
+ message,
+ Snackbar.LENGTH_INDEFINITE);
+ messageSnackbar.getView().setBackgroundColor(0xbf323232);
+ if (finishOnDismiss) {
+ messageSnackbar.setAction(
+ "Dismiss",
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ messageSnackbar.dismiss();
+ }
+ });
+ messageSnackbar.addCallback(
+ new BaseTransientBottomBar.BaseCallback() {
+ @Override
+ public void onDismissed(Snackbar transientBottomBar, int event) {
+ super.onDismissed(transientBottomBar, event);
+ finish();
+ }
+ });
+ }
+ messageSnackbar.show();
+ }
+
+ private void toast(int stringResId){
+ Toast.makeText(this, stringResId, Toast.LENGTH_SHORT).show();
+ }
+ private boolean isVerticalMode = false;
+ private PopupWindow popupWindow;
+ private PopupWindow getPopupWindow() {
+
+ // initialize a pop up window type
+ popupWindow = new PopupWindow(this);
+
+ ArrayList sortList = new ArrayList<>();
+ sortList.add(getString(R.string.action_1));
+ sortList.add(getString(R.string.action_2));
+ sortList.add(getString(R.string.action_3));
+ sortList.add(getString(R.string.action_4));
+
+ ArrayAdapter adapter = new ArrayAdapter<>(this, android.R.layout.simple_dropdown_item_1line,
+ sortList);
+ // the drop down list is a list view
+ ListView listViewSort = new ListView(this);
+ // set our adapter and pass our pop up window contents
+ listViewSort.setAdapter(adapter);
+ listViewSort.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
+ @Override
+ public boolean onItemLongClick(AdapterView> parent, View view, int position, long id) {
+ switch (position){
+ case 3:// move vertical axis
+ toast(R.string.action_4_toast);
+ break;
+ case 0:// delete
+ toast(R.string.action_1_toast);
+ break;
+ case 1:// set as first
+ toast(R.string.action_2_toast);
+ break;
+ case 2:// move horizontal axis
+ default:
+ toast(R.string.action_3_toast);
+ break;
+ }
+ return true;
+ }
+ });
+ // set on item selected
+ listViewSort.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView> parent, View view, int position, long id) {
+ switch (position){
+ case 3:// move vertical axis
+ isVerticalMode = true;
+ popupWindow.dismiss();
+ break;
+ case 0:// delete
+ glSerfaceRenderer.deleteNowSelection();
+ popupWindow.dismiss();
+ fab.hide();
+ break;
+ case 1:// set as first
+ glSerfaceRenderer.setNowSelectionAsFirst();
+ popupWindow.dismiss();
+ fab.hide();
+ break;
+ case 2:// move horizontal axis
+ default:
+ isVerticalMode = false;
+ popupWindow.dismiss();
+ break;
+ }
+
+ }
+ });
+ // some other visual settings for popup window
+ popupWindow.setFocusable(true);
+ popupWindow.setWidth((int)(getResources().getDisplayMetrics().widthPixels * 0.4f));
+ // popupWindow.setBackgroundDrawable(getResources().getDrawable(R.drawable.white));
+ popupWindow.setHeight(WindowManager.LayoutParams.WRAP_CONTENT);
+ // set the listview as popup content
+ popupWindow.setContentView(listViewSort);
+ return popupWindow;
+ }
+
+ private class GLSurfaceRenderer implements GLSurfaceView.Renderer{
+ private static final String TAG = "GLSurfaceRenderer";
+ private Context context;
+ private final int DEFAULT_VALUE = -1;
+ private int nowTouchingPointIndex = DEFAULT_VALUE;
+ private int viewWidth = 0;
+ private int viewHeight = 0;
+ // according to cube.obj, cube diameter = 0.02f
+ private final float cubeHitAreaRadius = 0.08f;
+ private final float[] centerVertexOfCube = {0f, 0f, 0f, 1};
+ private final float[] vertexResult = new float[4];
+
+ private float[] tempTranslation = new float[3];
+ private float[] tempRotation = new float[4];
+ private float[] projmtx = new float[16];
+ private float[] viewmtx = new float[16];
+
+ public GLSurfaceRenderer(Context context){
+ this.context = context;
+ }
+
+ @Override
+ public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+ logStatus("onSurfaceCreated()");
+ GLES20.glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
+
+ // Create the texture and pass it to ARCore session to be filled during update().
+ backgroundRenderer.createOnGlThread(context);
+ if (session != null) {
+ session.setCameraTextureName(backgroundRenderer.getTextureId());
+ }
+
+ // Prepare the other rendering objects.
+ try {
+ rectRenderer = new RectanglePolygonRenderer();
+ cube.createOnGlThread(context, ASSET_NAME_CUBE_OBJ, ASSET_NAME_CUBE);
+ cube.setMaterialProperties(0.0f, 3.5f, 1.0f, 6.0f);
+ cubeSelected.createOnGlThread(context, ASSET_NAME_CUBE_OBJ, ASSET_NAME_CUBE_SELECTED);
+ cubeSelected.setMaterialProperties(0.0f, 3.5f, 1.0f, 6.0f);
+ } catch (IOException e) {
+ log(TAG, "Failed to read obj file");
+ }
+ try {
+ planeRenderer.createOnGlThread(context, "trigrid.png");
+ } catch (IOException e) {
+ log(TAG, "Failed to read plane texture");
+ }
+ pointCloud.createOnGlThread(context);
+ }
+
+ @Override
+ public void onSurfaceChanged(GL10 gl, int width, int height) {
+ if(width <= 0 || height <= 0){
+ logStatus("onSurfaceChanged(), <= 0");
+ return;
+ }
+ logStatus("onSurfaceChanged()");
+
+ displayRotationHelper.onSurfaceChanged(width, height);
+ GLES20.glViewport(0, 0, width, height);
+ viewWidth = width;
+ viewHeight = height;
+ setNowTouchingPointIndex(DEFAULT_VALUE);
+ }
+
+ public void deleteNowSelection(){
+ logStatus("deleteNowSelection()");
+ int index = nowTouchingPointIndex;
+ if (index > -1){
+ if(index < anchors.size()) {
+ anchors.remove(index).detach();
+ }
+ if(index < showingTapPointX.size()) {
+ showingTapPointX.remove(index);
+ }
+ if(index < showingTapPointY.size()) {
+ showingTapPointY.remove(index);
+ }
+ }
+ setNowTouchingPointIndex(DEFAULT_VALUE);
+ }
+
+ public void setNowSelectionAsFirst(){
+ logStatus("setNowSelectionAsFirst()");
+ int index = nowTouchingPointIndex;
+ if (index > -1 && index < anchors.size()) {
+ if(index < anchors.size()){
+ for(int i=0; i= 16) {
+ anchors.get(0).detach();
+ anchors.remove(0);
+
+ showingTapPointX.remove(0);
+ showingTapPointY.remove(0);
+ }
+
+ // Adding an Anchor tells ARCore that it should track this position in
+ // space. This anchor will be used in PlaneAttachment to place the 3d model
+ // in the correct position relative both to the world and to the plane.
+ anchors.add(hit.createAnchor());
+
+ showingTapPointX.add(tap.getX());
+ showingTapPointY.add(tap.getY());
+ nowTouchingPointIndex = anchors.size() - 1;
+
+ showMoreAction();
+ showCubeStatus();
+ break;
+ }
+ }
+ }else{
+ handleMoveEvent(nowTouchingPointIndex);
+ }
+ } catch (Throwable t) {
+ // Avoid crashing the application due to unhandled exceptions.
+ Log.e(TAG, "Exception on the OpenGL thread", t);
+ }
+ }
+
+ private void handleMoveEvent(int nowSelectedIndex){
+ try {
+ if (showingTapPointX.size() < 1 || queuedScrollDx.size() < 2) {
+ // no action, don't move
+ return;
+ }
+ if (nowTouchingPointIndex == DEFAULT_VALUE) {
+ // no selected cube, don't move
+ return;
+ }
+ if (nowSelectedIndex >= showingTapPointX.size()) {
+ // wrong index, don't move.
+ return;
+ }
+ float scrollDx = 0;
+ float scrollDy = 0;
+ int scrollQueueSize = queuedScrollDx.size();
+ for (int i = 0; i < scrollQueueSize; i++) {
+ scrollDx += queuedScrollDx.poll();
+ scrollDy += queuedScrollDy.poll();
+ }
+
+ if (isVerticalMode) {
+ Anchor anchor = anchors.remove(nowSelectedIndex);
+ anchor.detach();
+ setPoseDataToTempArray(getPose(anchor));
+// log(TAG, "point[" + nowSelectedIndex + "] move vertical "+ (scrollDy / viewHeight) + ", tY=" + tempTranslation[1]
+// + ", new tY=" + (tempTranslation[1] += (scrollDy / viewHeight)));
+ tempTranslation[1] += (scrollDy / viewHeight);
+ anchors.add(nowSelectedIndex,
+ session.createAnchor(new Pose(tempTranslation, tempRotation)));
+ } else {
+ float toX = showingTapPointX.get(nowSelectedIndex) - scrollDx;
+ showingTapPointX.remove(nowSelectedIndex);
+ showingTapPointX.add(nowSelectedIndex, toX);
+
+ float toY = showingTapPointY.get(nowSelectedIndex) - scrollDy;
+ showingTapPointY.remove(nowSelectedIndex);
+ showingTapPointY.add(nowSelectedIndex, toY);
+
+ if (anchors.size() > nowSelectedIndex) {
+ Anchor anchor = anchors.remove(nowSelectedIndex);
+ anchor.detach();
+ // remove duplicated anchor
+ setPoseDataToTempArray(getPose(anchor));
+ tempTranslation[0] -= (scrollDx / viewWidth);
+ tempTranslation[2] -= (scrollDy / viewHeight);
+ anchors.add(nowSelectedIndex,
+ session.createAnchor(new Pose(tempTranslation, tempRotation)));
+ }
+ }
+ } catch (NotTrackingException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private final float[] mPoseTranslation = new float[3];
+ private final float[] mPoseRotation = new float[4];
+ private Pose getPose(Anchor anchor){
+ Pose pose = anchor.getPose();
+ pose.getTranslation(mPoseTranslation, 0);
+ pose.getRotationQuaternion(mPoseRotation, 0);
+ return new Pose(mPoseTranslation, mPoseRotation);
+ }
+
+ private void setPoseDataToTempArray(Pose pose){
+ pose.getTranslation(tempTranslation, 0);
+ pose.getRotationQuaternion(tempRotation, 0);
+ }
+
+ private void drawLine(Pose pose0, Pose pose1, float[] viewmtx, float[] projmtx){
+ float lineWidth = 0.002f;
+ float lineWidthH = lineWidth / viewHeight * viewWidth;
+ rectRenderer.setVerts(
+ pose0.tx() - lineWidth, pose0.ty() + lineWidthH, pose0.tz() - lineWidth,
+ pose0.tx() + lineWidth, pose0.ty() + lineWidthH, pose0.tz() + lineWidth,
+ pose1.tx() + lineWidth, pose1.ty() + lineWidthH, pose1.tz() + lineWidth,
+ pose1.tx() - lineWidth, pose1.ty() + lineWidthH, pose1.tz() - lineWidth
+ ,
+ pose0.tx() - lineWidth, pose0.ty() - lineWidthH, pose0.tz() - lineWidth,
+ pose0.tx() + lineWidth, pose0.ty() - lineWidthH, pose0.tz() + lineWidth,
+ pose1.tx() + lineWidth, pose1.ty() - lineWidthH, pose1.tz() + lineWidth,
+ pose1.tx() - lineWidth, pose1.ty() - lineWidthH, pose1.tz() - lineWidth
+ );
+
+ rectRenderer.draw(viewmtx, projmtx);
+ }
+
+ private void drawObj(Pose pose, ObjectRenderer renderer, float[] cameraView, float[] cameraPerspective, float lightIntensity){
+ pose.toMatrix(anchorMatrix, 0);
+ renderer.updateModelMatrix(anchorMatrix, 1);
+ renderer.draw(cameraView, cameraPerspective, lightIntensity);
+ }
+
+ private void checkIfHit(ObjectRenderer renderer, int cubeIndex){
+ if(isMVPMatrixHitMotionEvent(renderer.getModelViewProjectionMatrix(), queuedLongPress.peek())){
+ // long press hit a cube, show context menu for the cube
+ nowTouchingPointIndex = cubeIndex;
+ queuedLongPress.poll();
+ showMoreAction();
+ showCubeStatus();
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ fab.performClick();
+ }
+ });
+ }else if(isMVPMatrixHitMotionEvent(renderer.getModelViewProjectionMatrix(), queuedSingleTaps.peek())){
+ nowTouchingPointIndex = cubeIndex;
+ queuedSingleTaps.poll();
+ showMoreAction();
+ showCubeStatus();
+ }
+ }
+
+ private boolean isMVPMatrixHitMotionEvent(float[] ModelViewProjectionMatrix, MotionEvent event){
+ if(event == null){
+ return false;
+ }
+ Matrix.multiplyMV(vertexResult, 0, ModelViewProjectionMatrix, 0, centerVertexOfCube, 0);
+ /**
+ * vertexResult = [x, y, z, w]
+ *
+ * coordinates in View
+ * ┌─────────────────────────────────────────┐╮
+ * │[0, 0] [viewWidth, 0]│
+ * │ [viewWidth/2, viewHeight/2] │view height
+ * │[0, viewHeight] [viewWidth, viewHeight]│
+ * └─────────────────────────────────────────┘╯
+ * ╰ view width ╯
+ *
+ * coordinates in GLSurfaceView frame
+ * ┌─────────────────────────────────────────┐╮
+ * │[-1.0, 1.0] [1.0, 1.0]│
+ * │ [0, 0] │view height
+ * │[-1.0, -1.0] [1.0, -1.0]│
+ * └─────────────────────────────────────────┘╯
+ * ╰ view width ╯
+ */
+ // circle hit test
+ float radius = (viewWidth / 2) * (cubeHitAreaRadius/vertexResult[3]);
+ float dx = event.getX() - (viewWidth / 2) * (1 + vertexResult[0]/vertexResult[3]);
+ float dy = event.getY() - (viewHeight / 2) * (1 - vertexResult[1]/vertexResult[3]);
+ double distance = Math.sqrt(dx * dx + dy * dy);
+// // for debug
+// overlayViewForTest.setPoint("cubeCenter", screenX, screenY);
+// overlayViewForTest.postInvalidate();
+ return distance < radius;
+ }
+
+ private double getDistance(Pose pose0, Pose pose1){
+ float dx = pose0.tx() - pose1.tx();
+ float dy = pose0.ty() - pose1.ty();
+ float dz = pose0.tz() - pose1.tz();
+ return Math.sqrt(dx * dx + dz * dz + dy * dy);
+ }
+
+ private void showResult(final String result){
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ tv_result.setText(result);
+ }
+ });
+ }
+
+ private void showMoreAction(){
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ fab.show();
+ }
+ });
+ }
+
+ private void hideMoreAction(){
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ fab.hide();
+ }
+ });
+ }
+
+ private void showCubeStatus(){
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ int nowSelectIndex = glSerfaceRenderer.getNowTouchingPointIndex();
+ for(int i = 0; i pointMap = new HashMap<>();
+
+ public void setPoint(String tag, float x, float y){
+ pointMap.put(tag, new Point((int)x, (int)y));
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ if(pointMap == null){
+ return;
+ }
+
+ for(String key : pointMap.keySet()){
+ Point point = pointMap.get(key);
+ canvas.drawCircle(point.x, point.y, 20, paint);
+ Log.d("OverlayView", "drawCircle");
+ }
+
+ }
+
+}
diff --git a/app/src/main/java/com/hl3hl3/arcoremeasure/renderer/LineRenderer.java b/app/src/main/java/com/hl3hl3/arcoremeasure/renderer/LineRenderer.java
new file mode 100644
index 0000000..a82f43f
--- /dev/null
+++ b/app/src/main/java/com/hl3hl3/arcoremeasure/renderer/LineRenderer.java
@@ -0,0 +1,161 @@
+package com.hl3hl3.arcoremeasure.renderer;
+
+import android.opengl.GLES20;
+import android.opengl.Matrix;
+
+import com.google.ar.core.examples.java.helloar.rendering.ShaderUtil;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+
+
+public class LineRenderer {
+
+ private final int mProgram;
+
+ private final String vertexShaderCode =
+ // This matrix member variable provides a hook to manipulate
+ // the coordinates of the objects that use this vertex shader
+ "uniform mat4 uMVPMatrix;" +
+ "attribute vec4 vPosition;" +
+ "void main() {" +
+ // the matrix must be included as a modifier of gl_Position
+ // Note that the uMVPMatrix factor *must be first* in order
+ // for the matrix multiplication product to be correct.
+ " gl_Position = uMVPMatrix * vPosition;" +
+ "}";
+
+ // Use to access and set the view transformation
+ private int mMVPMatrixHandle;
+
+ private final String fragmentShaderCode =
+ "precision mediump float;" +
+ "uniform vec4 vColor;" +
+ "void main() {" +
+ " gl_FragColor = vColor;" +
+ "}";
+
+
+ private FloatBuffer vertexBuffer;
+
+ static final int COORDS_PER_VERTEX = 3;
+ static float coordinates[] = { // in counterclockwise order:
+ 0.0f, 0.0f, 0.0f, // point 1
+ 1.0f, 0.0f, 0.0f, // point 2
+ };
+
+ float color[] = {0.63671875f, 0.76953125f, 0.22265625f, 1.0f};
+
+ private int mPositionHandle;
+ private int mColorHandle;
+
+ private final int vertexCount = coordinates.length / COORDS_PER_VERTEX;
+ private final int vertexStride = COORDS_PER_VERTEX * 4;
+
+ private int loadShader(int type, String shaderCode){
+ int shader = GLES20.glCreateShader(type);
+ GLES20.glShaderSource(shader, shaderCode);
+ GLES20.glCompileShader(shader);
+ return shader;
+ }
+
+ public LineRenderer() {
+ // initialize vertex byte buffer for shape coordinates
+ // number ofr coordinate values * 4 bytes per float
+ ByteBuffer bb = ByteBuffer.allocateDirect(coordinates.length * 4);
+ bb.order(ByteOrder.nativeOrder()); // use the device hardware's native byte order
+ vertexBuffer = bb.asFloatBuffer(); // create a floating point buffer from the ByteBuffer
+ vertexBuffer.put(coordinates); // add the coordinate to the FloatBuffer
+ vertexBuffer.position(0); // set the buffer to read the first coordinate
+
+ int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
+ int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
+
+ mProgram = GLES20.glCreateProgram(); // create empty OpenGL ES Program
+ GLES20.glAttachShader(mProgram, vertexShader); // add the shader to program
+ GLES20.glAttachShader(mProgram, fragmentShader);
+ GLES20.glLinkProgram(mProgram); // create OpenGL ES program executables
+ Matrix.setIdentityM(mModelMatrix, 0);
+ }
+
+ public void setVerts(float v0, float v1, float v2, float v3, float v4, float v5) {
+ coordinates[0] = v0;
+ coordinates[1] = v1;
+ coordinates[2] = v2;
+ coordinates[3] = v3;
+ coordinates[4] = v4;
+ coordinates[5] = v5;
+
+ vertexBuffer.put(coordinates);
+ // set the buffer to read the first coordinate
+ vertexBuffer.position(0);
+ }
+
+ public void setColor(float red, float green, float blue, float alpha) {
+ color[0] = red;
+ color[1] = green;
+ color[2] = blue;
+ color[3] = alpha;
+ }
+
+ // Temporary matrices allocated here to reduce number of allocations for each frame.
+ private float[] mModelMatrix = new float[16];
+ private float[] mModelViewMatrix = new float[16];
+ private float[] mModelViewProjectionMatrix = new float[16];
+ final String TAG = "Line";
+
+ public void draw(float[] cameraView, float[] cameraPerspective) {
+ ShaderUtil.checkGLError(TAG, "Before draw");
+
+ // Build the ModelView and ModelViewProjection matrices
+ // for calculating object position and light.
+ Matrix.multiplyMM(mModelViewMatrix, 0, cameraView, 0, mModelMatrix, 0);
+ Matrix.multiplyMM(mModelViewProjectionMatrix, 0, cameraPerspective, 0, mModelViewMatrix, 0);
+
+ // add program to OpenGL ES environment
+ GLES20.glUseProgram(mProgram);
+ ShaderUtil.checkGLError(TAG, "After glBindBuffer");
+
+ GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
+ ShaderUtil.checkGLError(TAG, "After glBindBuffer");
+
+ // get handle to vertex shader's vPosition member
+ mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
+ ShaderUtil.checkGLError(TAG, "After glGetAttribLocation");
+
+ // enable a handle to the vertices
+ GLES20.glEnableVertexAttribArray(mPositionHandle);
+ ShaderUtil.checkGLError(TAG, "After glEnableVertexAttribArray");
+
+ // prepare the coordinate data
+ GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
+ GLES20.GL_FLOAT, false,
+ vertexStride, vertexBuffer);
+ ShaderUtil.checkGLError(TAG, "After glVertexAttribPointer");
+
+ // get handle to fragment shader's vColor member
+ mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
+
+ // set color for drawing the triangle
+ GLES20.glUniform4fv(mColorHandle, 1, color, 0);
+ ShaderUtil.checkGLError(TAG, "After glUniform4fv");
+
+ // get handle to shape's transformation matrix
+ mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
+
+ // Pass the projection and view transformation to the shader
+ GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mModelViewProjectionMatrix, 0);
+ ShaderUtil.checkGLError(TAG, "After glUniformMatrix4fv");
+
+ // Draw the line
+ GLES20.glDrawArrays(GLES20.GL_LINES, 0, vertexCount);
+ ShaderUtil.checkGLError(TAG, "After glDrawArrays");
+
+ // Disable vertex array
+ GLES20.glDisableVertexAttribArray(mPositionHandle);
+ ShaderUtil.checkGLError(TAG, "After draw");
+ }
+
+
+}
diff --git a/app/src/main/java/com/hl3hl3/arcoremeasure/renderer/RectanglePolygonRenderer.java b/app/src/main/java/com/hl3hl3/arcoremeasure/renderer/RectanglePolygonRenderer.java
new file mode 100644
index 0000000..1abfbf6
--- /dev/null
+++ b/app/src/main/java/com/hl3hl3/arcoremeasure/renderer/RectanglePolygonRenderer.java
@@ -0,0 +1,206 @@
+package com.hl3hl3.arcoremeasure.renderer;
+
+import android.opengl.GLES20;
+import android.opengl.Matrix;
+
+import com.google.ar.core.examples.java.helloar.rendering.ShaderUtil;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+
+public class RectanglePolygonRenderer {
+
+ private FloatBuffer vertexBuffer;
+ private ShortBuffer drawListBuffer;
+
+ // number of coordinates pervertex in this array
+ static final int COORDS_PER_VERTEX = 3;
+ static float coords[] = {
+ -0.6f, 0.5f, 0.2f, // top left
+ -0.4f, -0.5f, 0.2f, // bottom left
+ 0.5f, -0.5f, 0.2f, // bottom right
+ 0.5f, 0.5f, 0.2f, // top right
+
+ -0.5f, 0.6f, 0.0f, // top left
+ -0.5f, -0.8f, 0.0f, // bottom left
+ 0.5f, -0.5f, 0.0f, // bottom right
+ 0.5f, 0.5f, 0.0f // top right
+ };
+ private short drawOrder[] = {
+ 0, 1, 2, 0, 2, 3,
+ 3, 2, 6, 3, 6, 7,
+ 4, 5, 6, 4, 6, 7,
+ 0, 1, 5, 0, 5, 4,
+ 4, 0, 3, 4, 3, 7,
+ 5, 1, 2, 5, 2, 6
+ }; // order to draw vertex
+
+ float color[] = {0.63671875f, 0.76953125f, 0.22265625f, 1.0f};
+
+ public void setVerts(float v0, float v1, float v2,
+ float v3, float v4, float v5,
+ float v6, float v7, float v8,
+ float v9, float v10, float v11,
+
+ float v12, float v13, float v14,
+ float v15, float v16, float v17,
+ float v18, float v19, float v20,
+ float v21, float v22, float v23
+ ) {
+ coords[0] = v0;
+ coords[1] = v1;
+ coords[2] = v2;
+
+ coords[3] = v3;
+ coords[4] = v4;
+ coords[5] = v5;
+
+ coords[6] = v6;
+ coords[7] = v7;
+ coords[8] = v8;
+
+ coords[9] = v9;
+ coords[10] = v10;
+ coords[11] = v11;
+
+ coords[12] = v12;
+ coords[13] = v13;
+ coords[14] = v14;
+
+ coords[15] = v15;
+ coords[16] = v16;
+ coords[17] = v17;
+
+ coords[18] = v18;
+ coords[19] = v19;
+ coords[20] = v20;
+
+ coords[21] = v21;
+ coords[22] = v22;
+ coords[23] = v23;
+
+ vertexBuffer.put(coords);
+ // set the buffer to read the first coordinate
+ vertexBuffer.position(0);
+ }
+
+ public void setColor(float red, float green, float blue, float alpha) {
+ color[0] = red;
+ color[1] = green;
+ color[2] = blue;
+ color[3] = alpha;
+ }
+
+
+ private final int mProgram;
+
+ private final String vertexShaderCode =
+ "uniform mat4 uMVPMatrix;" +
+ "attribute vec4 vPosition;" +
+ "void main() {" +
+ " gl_Position = uMVPMatrix * vPosition;" +
+ "}";
+
+ // Use to access and set the view transformation
+ private int mMVPMatrixHandle;
+
+ private final String fragmentShaderCode =
+ "precision mediump float;" +
+ "uniform vec4 vColor;" +
+ "void main() {" +
+ " gl_FragColor = vColor;" +
+ "}";
+
+ private int mPositionHandle;
+ private int mColorHandle;
+
+ private final int vertexCount = coords.length / COORDS_PER_VERTEX;
+ private final int vertexStride = COORDS_PER_VERTEX * 4;
+
+ private int loadShader(int type, String shaderCode){
+ int shader = GLES20.glCreateShader(type);
+ GLES20.glShaderSource(shader, shaderCode);
+ GLES20.glCompileShader(shader);
+ return shader;
+ }
+
+ public RectanglePolygonRenderer(){
+ // initialize vertex byte buffer for shape coordinates
+ ByteBuffer bb = ByteBuffer.allocateDirect(coords.length * 4);
+ bb.order(ByteOrder.nativeOrder());
+ vertexBuffer = bb.asFloatBuffer();
+ vertexBuffer.put(coords);
+ vertexBuffer.position(0);
+
+ // initialize byte buffer for the draw list
+ ByteBuffer dlb = ByteBuffer.allocateDirect(drawOrder.length * 2); // 2 bytes per short
+ dlb.order(ByteOrder.nativeOrder());
+ drawListBuffer = dlb.asShortBuffer();
+ drawListBuffer.put(drawOrder);
+ drawListBuffer.position(0);
+
+ int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
+ int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
+
+ // create empty OpenGL ES Program
+ mProgram = GLES20.glCreateProgram();
+
+ // add the shader to program
+ GLES20.glAttachShader(mProgram, vertexShader);
+ GLES20.glAttachShader(mProgram, fragmentShader);
+
+ // create OpenGL ES program executables
+ GLES20.glLinkProgram(mProgram);
+ Matrix.setIdentityM(mModelMatrix, 0);
+ }
+
+ // Temporary matrices allocated here to reduce number of allocations for each frame.
+ private float[] mModelMatrix = new float[16];
+ private float[] mModelViewMatrix = new float[16];
+ private float[] mModelViewProjectionMatrix = new float[16];
+ final String TAG = "RectanglePolygon";
+
+ public void draw(float[] cameraView, float[] cameraPerspective) {
+ ShaderUtil.checkGLError(TAG, "Before draw");
+
+ // Build the ModelView and ModelViewProjection matrices
+ // for calculating object position and light.
+ Matrix.multiplyMM(mModelViewMatrix, 0, cameraView, 0, mModelMatrix, 0);
+ Matrix.multiplyMM(mModelViewProjectionMatrix, 0, cameraPerspective, 0, mModelViewMatrix, 0);
+
+ // add program to OpenGL ES environment
+ GLES20.glUseProgram(mProgram);
+
+ // get handle to vertex shader's vPosition member
+ mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
+
+ // enable a handle to the triangle vertices
+ GLES20.glEnableVertexAttribArray(mPositionHandle);
+
+ // prepare the triangle coordinate data
+ GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
+ GLES20.GL_FLOAT, false,
+ vertexStride, vertexBuffer);
+
+ // get handle to fragment shader's vColor member
+ mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
+
+ // set color for drawing the triangle
+ GLES20.glUniform4fv(mColorHandle, 1, color, 0);
+
+ // get handle to shape's transformation matrix
+ mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
+
+ // Pass the projection and view transformation to the shader
+ GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mModelViewProjectionMatrix, 0);
+
+ GLES20.glDrawElements(GLES20.GL_TRIANGLES, drawOrder.length,
+ GLES20.GL_UNSIGNED_SHORT, drawListBuffer);
+
+ // Disable vertex array
+ GLES20.glDisableVertexAttribArray(mPositionHandle);
+ }
+}
diff --git a/app/src/main/java/com/hl3hl3/arcoremeasure/renderer/SquareRenderer.java b/app/src/main/java/com/hl3hl3/arcoremeasure/renderer/SquareRenderer.java
new file mode 100644
index 0000000..393711e
--- /dev/null
+++ b/app/src/main/java/com/hl3hl3/arcoremeasure/renderer/SquareRenderer.java
@@ -0,0 +1,175 @@
+package com.hl3hl3.arcoremeasure.renderer;
+
+import android.opengl.GLES20;
+import android.opengl.Matrix;
+
+import com.google.ar.core.examples.java.helloar.rendering.ShaderUtil;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+
+public class SquareRenderer {
+
+ private FloatBuffer vertexBuffer;
+ private ShortBuffer drawListBuffer;
+
+ // number of coordinates pervertex in this array
+ static final int COORDS_PER_VERTEX = 3;
+ static float coords[] = {
+ -0.05f, 0.05f, 0.0f, // top left
+ -0.05f, -0.05f, 0.0f, // bottom left
+ 0.05f, -0.05f, 0.0f, // bottom right
+ 0.05f, 0.05f, 0.0f }; // top right
+ private short drawOrder[] = {0, 1, 2, 0, 2, 3}; // order to draw vertex
+
+ float color[] = {0.63671875f, 0.76953125f, 0.22265625f, 1.0f};
+
+ public void setVerts(float v0, float v1, float v2,
+ float v3, float v4, float v5,
+ float v6, float v7, float v8,
+ float v9, float v10, float v11) {
+ coords[0] = v0;
+ coords[1] = v1;
+ coords[2] = v2;
+
+ coords[3] = v3;
+ coords[4] = v4;
+ coords[5] = v5;
+
+ coords[6] = v6;
+ coords[7] = v7;
+ coords[8] = v8;
+
+ coords[9] = v9;
+ coords[10] = v10;
+ coords[11] = v11;
+
+ vertexBuffer.put(coords);
+ // set the buffer to read the first coordinate
+ vertexBuffer.position(0);
+ }
+
+ public void setColor(float red, float green, float blue, float alpha) {
+ color[0] = red;
+ color[1] = green;
+ color[2] = blue;
+ color[3] = alpha;
+ }
+
+
+
+ private final int mProgram;
+
+ private final String vertexShaderCode =
+ "uniform mat4 uMVPMatrix;" +
+ "attribute vec4 vPosition;" +
+ "void main() {" +
+ " gl_Position = uMVPMatrix * vPosition;" +
+ "}";
+
+ // Use to access and set the view transformation
+ private int mMVPMatrixHandle;
+
+ private final String fragmentShaderCode =
+ "precision mediump float;" +
+ "uniform vec4 vColor;" +
+ "void main() {" +
+ " gl_FragColor = vColor;" +
+ "}";
+
+ private int mPositionHandle;
+ private int mColorHandle;
+
+ private final int vertexCount = coords.length / COORDS_PER_VERTEX;
+ private final int vertexStride = COORDS_PER_VERTEX * 4;
+
+ private int loadShader(int type, String shaderCode){
+ int shader = GLES20.glCreateShader(type);
+ GLES20.glShaderSource(shader, shaderCode);
+ GLES20.glCompileShader(shader);
+ return shader;
+ }
+
+ public SquareRenderer(){
+ // initialize vertex byte buffer for shape coordinates
+ ByteBuffer bb = ByteBuffer.allocateDirect(coords.length * 4);
+ bb.order(ByteOrder.nativeOrder());
+ vertexBuffer = bb.asFloatBuffer();
+ vertexBuffer.put(coords);
+ vertexBuffer.position(0);
+
+ // initialize byte buffer for the draw list
+ ByteBuffer dlb = ByteBuffer.allocateDirect(drawOrder.length * 2); // 2 bytes per short
+ dlb.order(ByteOrder.nativeOrder());
+ drawListBuffer = dlb.asShortBuffer();
+ drawListBuffer.put(drawOrder);
+ drawListBuffer.position(0);
+
+
+
+ int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
+ int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
+
+ // create empty OpenGL ES Program
+ mProgram = GLES20.glCreateProgram();
+
+ // add the shader to program
+ GLES20.glAttachShader(mProgram, vertexShader);
+ GLES20.glAttachShader(mProgram, fragmentShader);
+
+ // create OpenGL ES program executables
+ GLES20.glLinkProgram(mProgram);
+ Matrix.setIdentityM(mModelMatrix, 0);
+ }
+
+ // Temporary matrices allocated here to reduce number of allocations for each frame.
+ private float[] mModelMatrix = new float[16];
+ private float[] mModelViewMatrix = new float[16];
+ private float[] mModelViewProjectionMatrix = new float[16];
+ final String TAG = "Rectangle";
+
+ public void draw(float[] cameraView, float[] cameraPerspective) { // pass in the calculated transformation matrix
+
+ ShaderUtil.checkGLError(TAG, "Before draw");
+
+ // Build the ModelView and ModelViewProjection matrices
+ // for calculating object position and light.
+ Matrix.multiplyMM(mModelViewMatrix, 0, cameraView, 0, mModelMatrix, 0);
+ Matrix.multiplyMM(mModelViewProjectionMatrix, 0, cameraPerspective, 0, mModelViewMatrix, 0);
+
+ // add program to OpenGL ES environment
+ GLES20.glUseProgram(mProgram);
+
+ // get handle to vertex shader's vPosition member
+ mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
+
+ // enable a handle to the triangle vertices
+ GLES20.glEnableVertexAttribArray(mPositionHandle);
+
+ // prepare the triangle coordinate data
+ GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
+ GLES20.GL_FLOAT, false,
+ vertexStride, vertexBuffer);
+
+ // get handle to fragment shader's vColor member
+ mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
+
+ // set color for drawing the triangle
+ GLES20.glUniform4fv(mColorHandle, 1, color, 0);
+
+ // get handle to shape's transformation matrix
+ mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
+
+ // Pass the projection and view transformation to the shader
+ GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mModelViewProjectionMatrix, 0);
+
+ GLES20.glDrawElements(GLES20.GL_TRIANGLES, drawOrder.length,
+ GLES20.GL_UNSIGNED_SHORT, drawListBuffer);
+
+ // Disable vertex array
+ GLES20.glDisableVertexAttribArray(mPositionHandle);
+ }
+}
diff --git a/app/src/main/java/drawar/AppSettings.java b/app/src/main/java/drawar/AppSettings.java
new file mode 100644
index 0000000..9f91f82
--- /dev/null
+++ b/app/src/main/java/drawar/AppSettings.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ package drawar;
+
+import javax.vecmath.Vector3f;
+
+public class AppSettings {
+ private static final Vector3f color = new Vector3f(1f, 1f, 1f);
+ private static final float strokeDrawDistance = 0.125f;
+ private static final float minDistance = 0.000625f;
+ private static final float nearClip = 0.001f;
+ private static final float farClip = 100.0f;
+
+
+ public static float getStrokeDrawDistance() {
+ return strokeDrawDistance;
+ }
+
+ public static Vector3f getColor() {
+ return color;
+ }
+
+ public static float getMinDistance() {
+ return minDistance;
+ }
+
+ public static float getNearClip(){
+ return nearClip;
+ }
+ public static float getFarClip(){
+ return farClip;
+ }
+
+}
diff --git a/app/src/main/java/drawar/BiquadFilter.java b/app/src/main/java/drawar/BiquadFilter.java
new file mode 100644
index 0000000..ec39de2
--- /dev/null
+++ b/app/src/main/java/drawar/BiquadFilter.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package drawar;
+
+import javax.vecmath.Vector3f;
+
+
+/**
+ * BiquadFilter is a object for easily lowpass filtering incomming values.
+ */
+public class BiquadFilter {
+ private Vector3f val = new Vector3f();
+
+ private BiquadFilterInstance[] inst = new BiquadFilterInstance[3];
+
+ BiquadFilter(double Fc){
+ for(int i=0;i<3;i++){
+ inst[i] = new BiquadFilterInstance(Fc);
+ }
+ }
+
+ Vector3f update(Vector3f in){
+ val.x = (float) inst[0].process(in.x);
+ val.y = (float) inst[1].process(in.y);
+ val.z = (float) inst[2].process(in.z);
+ return val;
+ }
+
+ private class BiquadFilterInstance {
+ double a0, a1, a2, b1, b2;
+ double Fc=0.5,Q=0.707, peakGain=0.0;
+ double z1=0.0, z2=0.0;
+
+ BiquadFilterInstance(double fc){
+ Fc = fc;
+ calcBiquad();
+ }
+
+ double process(double in){
+ double out = in * a0 + z1;
+ z1 = in * a1 + z2 - b1 * out;
+ z2 = in * a2 - b2 * out;
+ return out;
+ }
+
+ void calcBiquad() {
+ double norm;
+ double K = Math.tan(Math.PI * Fc);
+ norm = 1 / (1 + K / Q + K * K);
+ a0 = K * K * norm;
+ a1 = 2 * a0;
+ a2 = a0;
+ b1 = 2 * (K * K - 1) * norm;
+ b2 = (1 - K / Q + K * K) * norm;
+ }
+ }
+}
+
+
diff --git a/app/src/main/java/drawar/DisplayRotationHelper.java b/app/src/main/java/drawar/DisplayRotationHelper.java
new file mode 100755
index 0000000..de5a3b4
--- /dev/null
+++ b/app/src/main/java/drawar/DisplayRotationHelper.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package drawar;
+
+import android.app.Activity;
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManager.DisplayListener;
+import android.view.Display;
+import android.view.WindowManager;
+
+import com.google.ar.core.Session;
+
+/**
+ * Helper to track the display rotations. In particular, the 180 degree rotations are not notified
+ * by the onSurfaceChanged() callback, and thus they require listening to the android display
+ * events.
+ */
+public class DisplayRotationHelper implements DisplayListener {
+ private boolean viewportChanged;
+ private int viewportWidth;
+ private int viewportHeight;
+ private final Context context;
+ private final Display display;
+
+ /**
+ * Constructs the DisplayRotationHelper but does not register the listener yet.
+ *
+ * @param context the Android {@link Context}.
+ */
+ public DisplayRotationHelper(Context context) {
+ this.context = context;
+ display = context.getSystemService(WindowManager.class).getDefaultDisplay();
+ }
+
+
+ public void onResume() {
+ context.getSystemService(DisplayManager.class).registerDisplayListener(this, null);
+ }
+
+
+ public void onPause() {
+ context.getSystemService(DisplayManager.class).unregisterDisplayListener(this);
+ }
+
+ /**
+ * Records a change in surface dimensions. This will be later used by {@link
+ * #updateSessionIfNeeded(Session)}. Should be called from {@link
+ * android.opengl.GLSurfaceView.Renderer
+ * #onSurfaceChanged(javax.microedition.khronos.opengles.GL10, int, int)}.
+ *
+ * @param width the updated width of the surface.
+ * @param height the updated height of the surface.
+ */
+ public void onSurfaceChanged(int width, int height) {
+ viewportWidth = width;
+ viewportHeight = height;
+ viewportChanged = true;
+ }
+
+ /**
+ * Updates the session display geometry if a change was posted either by {@link
+ * #onSurfaceChanged(int, int)} call or by {@link #onDisplayChanged(int)} system callback. This
+ * function should be called explicitly before each call to {@link Session#update()}. This
+ * function will also clear the 'pending update' (viewportChanged) flag.
+ *
+ * @param session the {@link Session} object to update if display geometry changed.
+ */
+ public void updateSessionIfNeeded(Session session) {
+ if (viewportChanged) {
+ int displayRotation = display.getRotation();
+ session.setDisplayGeometry(displayRotation, viewportWidth, viewportHeight);
+ viewportChanged = false;
+ }
+ }
+
+ /**
+ * Returns the current rotation state of android display. Same as {@link Display#getRotation()}.
+ */
+ public int getRotation() {
+ return display.getRotation();
+ }
+
+ @Override
+ public void onDisplayAdded(int displayId) {}
+
+ @Override
+ public void onDisplayRemoved(int displayId) {}
+
+ @Override
+ public void onDisplayChanged(int displayId) {
+ viewportChanged = true;
+ }
+}
diff --git a/app/src/main/java/drawar/DrawAR.java b/app/src/main/java/drawar/DrawAR.java
new file mode 100755
index 0000000..22ec4a3
--- /dev/null
+++ b/app/src/main/java/drawar/DrawAR.java
@@ -0,0 +1,739 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package drawar;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.SharedPreferences;
+import android.opengl.GLES20;
+import android.opengl.GLSurfaceView;
+import android.opengl.Matrix;
+import android.os.Bundle;
+import android.support.design.widget.Snackbar;
+import android.support.v4.view.GestureDetectorCompat;
+import android.support.v7.app.AppCompatActivity;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.SeekBar;
+import android.widget.Toast;
+
+import com.google.ar.core.ArCoreApk;
+import com.google.ar.core.Camera;
+import com.google.ar.core.Config;
+import com.google.ar.core.Frame;
+import com.google.ar.core.Session;
+import com.google.ar.core.TrackingState;
+import com.google.ar.core.exceptions.CameraNotAvailableException;
+import com.google.ar.core.exceptions.UnavailableApkTooOldException;
+import com.google.ar.core.exceptions.UnavailableArcoreNotInstalledException;
+import com.google.ar.core.exceptions.UnavailableSdkTooOldException;
+import com.google.ar.core.exceptions.UnavailableUserDeclinedInstallationException;
+
+import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+import javax.vecmath.Vector2f;
+import javax.vecmath.Vector3f;
+
+import drawar.rendering.BackgroundRenderer;
+import drawar.rendering.LineShaderRenderer;
+import drawar.rendering.LineUtils;
+import fr.geolabs.dev.mapmint4me.R;
+
+
+/**
+ * This is a complex example that shows how to create an augmented reality (AR) application using
+ * the ARCore API.
+ */
+
+public class DrawAR extends AppCompatActivity implements GLSurfaceView.Renderer, GestureDetector.OnGestureListener,
+ GestureDetector.OnDoubleTapListener{
+ private static final String TAG = DrawAR.class.getSimpleName();
+
+ private GLSurfaceView mSurfaceView;
+
+ private Config mDefaultConfig;
+ private Session mSession;
+ private BackgroundRenderer mBackgroundRenderer = new BackgroundRenderer();
+ private LineShaderRenderer mLineShaderRenderer = new LineShaderRenderer();
+ private Frame mFrame;
+
+ private float[] projmtx = new float[16];
+ private float[] viewmtx = new float[16];
+ private float[] mZeroMatrix = new float[16];
+
+ private boolean mPaused = false;
+
+ private float mScreenWidth = 0;
+ private float mScreenHeight = 0;
+
+ private BiquadFilter biquadFilter;
+ private Vector3f mLastPoint;
+ private AtomicReference lastTouch = new AtomicReference<>();
+
+ private GestureDetectorCompat mDetector;
+
+ private LinearLayout mSettingsUI;
+ private LinearLayout mButtonBar;
+
+ private SeekBar mLineWidthBar;
+ private SeekBar mLineDistanceScaleBar;
+ private SeekBar mSmoothingBar;
+
+
+ private float mLineWidthMax = 0.33f;
+ private float mDistanceScale = 0.0f;
+ private float mLineSmoothing = 0.1f;
+
+ private float[] mLastFramePosition;
+
+ private AtomicBoolean bIsTracking = new AtomicBoolean(true);
+ private AtomicBoolean bReCenterView = new AtomicBoolean(false);
+ private AtomicBoolean bTouchDown = new AtomicBoolean(false);
+ private AtomicBoolean bClearDrawing = new AtomicBoolean(false);
+ private AtomicBoolean bLineParameters = new AtomicBoolean(false);
+ private AtomicBoolean bUndo = new AtomicBoolean(false);
+ private AtomicBoolean bNewStroke = new AtomicBoolean(false);
+
+ private ArrayList> mStrokes;
+
+ private DisplayRotationHelper mDisplayRotationHelper;
+ private Snackbar mMessageSnackbar;
+
+ private boolean bInstallRequested;
+
+ private TrackingState mState;
+ /**
+ * Setup the app when main activity is created
+ */
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ final SharedPreferences sharedPref = this.getPreferences(Context.MODE_PRIVATE);
+
+ setContentView(R.layout.act_layout);
+
+ mSurfaceView = findViewById(R.id.surfaceview);
+ mSettingsUI = findViewById(R.id.strokeUI);
+ mButtonBar = findViewById(R.id.button_bar);
+
+ // Settings seek bars
+ mLineDistanceScaleBar = findViewById(R.id.distanceScale);
+ mLineWidthBar = findViewById(R.id.lineWidth);
+ mSmoothingBar = findViewById(R.id.smoothingSeekBar);
+
+ mLineDistanceScaleBar.setProgress(sharedPref.getInt("mLineDistanceScale", 1));
+ mLineWidthBar.setProgress(sharedPref.getInt("mLineWidth", 10));
+ mSmoothingBar.setProgress(sharedPref.getInt("mSmoothing", 50));
+
+ mDistanceScale = LineUtils.map((float) mLineDistanceScaleBar.getProgress(), 0, 100, 1, 200, true);
+ mLineWidthMax = LineUtils.map((float) mLineWidthBar.getProgress(), 0f, 100f, 0.1f, 5f, true);
+ mLineSmoothing = LineUtils.map((float) mSmoothingBar.getProgress(), 0, 100, 0.01f, 0.2f, true);
+
+ SeekBar.OnSeekBarChangeListener seekBarChangeListener = new SeekBar.OnSeekBarChangeListener() {
+ /**
+ * Listen for seekbar changes, and update the settings
+ */
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ SharedPreferences.Editor editor = sharedPref.edit();
+
+ if (seekBar == mLineDistanceScaleBar) {
+ editor.putInt("mLineDistanceScale", progress);
+ mDistanceScale = LineUtils.map((float) progress, 0f, 100f, 1f, 200f, true);
+ } else if (seekBar == mLineWidthBar) {
+ editor.putInt("mLineWidth", progress);
+ mLineWidthMax = LineUtils.map((float) progress, 0f, 100f, 0.1f, 5f, true);
+ } else if (seekBar == mSmoothingBar) {
+ editor.putInt("mSmoothing", progress);
+ mLineSmoothing = LineUtils.map((float) progress, 0, 100, 0.01f, 0.2f, true);
+ }
+ mLineShaderRenderer.bNeedsUpdate.set(true);
+
+ editor.apply();
+
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ }
+ };
+
+ mLineDistanceScaleBar.setOnSeekBarChangeListener(seekBarChangeListener);
+ mLineWidthBar.setOnSeekBarChangeListener(seekBarChangeListener);
+ mSmoothingBar.setOnSeekBarChangeListener(seekBarChangeListener);
+
+ // Hide the settings ui
+ mSettingsUI.setVisibility(View.GONE);
+
+ mDisplayRotationHelper = new DisplayRotationHelper(/*context=*/ this);
+ // Reset the zero matrix
+ Matrix.setIdentityM(mZeroMatrix, 0);
+
+ mLastPoint = new Vector3f(0, 0, 0);
+
+ bInstallRequested = false;
+
+ // Set up renderer.
+ mSurfaceView.setPreserveEGLContextOnPause(true);
+ mSurfaceView.setEGLContextClientVersion(2);
+ mSurfaceView.setEGLConfigChooser(8, 8, 8, 8, 16, 0); // Alpha used for plane blending.
+ mSurfaceView.setRenderer(this);
+ mSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
+
+ // Setup touch detector
+ mDetector = new GestureDetectorCompat(this, this);
+ mDetector.setOnDoubleTapListener(this);
+ mStrokes = new ArrayList<>();
+
+
+ }
+
+
+ /**
+ * addStroke adds a new stroke to the scene
+ *
+ * @param touchPoint a 2D point in screen space and is projected into 3D world space
+ */
+ private void addStroke(Vector2f touchPoint) {
+ Vector3f newPoint = LineUtils.GetWorldCoords(touchPoint, mScreenWidth, mScreenHeight, projmtx, viewmtx);
+ addStroke(newPoint);
+ }
+
+
+ /**
+ * addPoint adds a point to the current stroke
+ *
+ * @param touchPoint a 2D point in screen space and is projected into 3D world space
+ */
+ private void addPoint(Vector2f touchPoint) {
+ Vector3f newPoint = LineUtils.GetWorldCoords(touchPoint, mScreenWidth, mScreenHeight, projmtx, viewmtx);
+ addPoint(newPoint);
+ }
+
+
+ /**
+ * addStroke creates a new stroke
+ *
+ * @param newPoint a 3D point in world space
+ */
+ private void addStroke(Vector3f newPoint) {
+ biquadFilter = new BiquadFilter(mLineSmoothing);
+ for (int i = 0; i < 1500; i++) {
+ biquadFilter.update(newPoint);
+ }
+ Vector3f p = biquadFilter.update(newPoint);
+ mLastPoint = new Vector3f(p);
+ mStrokes.add(new ArrayList());
+ mStrokes.get(mStrokes.size() - 1).add(mLastPoint);
+ }
+
+ /**
+ * addPoint adds a point to the current stroke
+ *
+ * @param newPoint a 3D point in world space
+ */
+ private void addPoint(Vector3f newPoint) {
+ if (LineUtils.distanceCheck(newPoint, mLastPoint)) {
+ Vector3f p = biquadFilter.update(newPoint);
+ mLastPoint = new Vector3f(p);
+ mStrokes.get(mStrokes.size() - 1).add(mLastPoint);
+ }
+ }
+
+
+ /**
+ * onResume part of the Android Activity Lifecycle
+ */
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ if (mSession == null) {
+ Exception exception = null;
+ String message = null;
+ try {
+ switch (ArCoreApk.getInstance().requestInstall(this, !bInstallRequested)) {
+ case INSTALL_REQUESTED:
+ bInstallRequested = true;
+ return;
+ case INSTALLED:
+ break;
+ }
+
+ // ARCore requires camera permissions to operate. If we did not yet obtain runtime
+ // permission on Android M and above, now is a good time to ask the user for it.
+ if (!PermissionHelper.hasCameraPermission(this)) {
+ PermissionHelper.requestCameraPermission(this);
+ return;
+ }
+
+ mSession = new Session(/* context= */ this);
+ } catch (UnavailableArcoreNotInstalledException
+ | UnavailableUserDeclinedInstallationException e) {
+ message = "Please install ARCore";
+ exception = e;
+ } catch (UnavailableApkTooOldException e) {
+ message = "Please update ARCore";
+ exception = e;
+ } catch (UnavailableSdkTooOldException e) {
+ message = "Please update this app";
+ exception = e;
+ } catch (Exception e) {
+ message = "This device does not support AR";
+ exception = e;
+ }
+
+ if (message != null) {
+ Log.e(TAG, "Exception creating session", exception);
+ return;
+ }
+
+ // Create default config and check if supported.
+ Config config = new Config(mSession);
+ if (!mSession.isSupported(config)) {
+ Log.e(TAG, "Exception creating session Device Does Not Support ARCore", exception);
+ }
+ mSession.configure(config);
+ }
+ // Note that order matters - see the note in onPause(), the reverse applies here.
+ try {
+ mSession.resume();
+ } catch (CameraNotAvailableException e) {
+ e.printStackTrace();
+ }
+ mSurfaceView.onResume();
+ mDisplayRotationHelper.onResume();
+ mPaused = false;
+ }
+
+ /**
+ * onPause part of the Android Activity Lifecycle
+ */
+ @Override
+ public void onPause() {
+ super.onPause();
+ // Note that the order matters - GLSurfaceView is paused first so that it does not try
+ // to query the session. If Session is paused before GLSurfaceView, GLSurfaceView may
+ // still call mSession.update() and get a SessionPausedException.
+
+ if (mSession != null) {
+ mDisplayRotationHelper.onPause();
+ mSurfaceView.onPause();
+ mSession.pause();
+ }
+
+ mPaused = false;
+
+
+ DisplayMetrics displayMetrics = new DisplayMetrics();
+ getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
+ mScreenHeight = displayMetrics.heightPixels;
+ mScreenWidth = displayMetrics.widthPixels;
+ }
+
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] results) {
+ if (!PermissionHelper.hasCameraPermission(this)) {
+ Toast.makeText(this,
+ "Camera permission is needed to run this application", Toast.LENGTH_LONG).show();
+ finish();
+ }
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ super.onWindowFocusChanged(hasFocus);
+ if (hasFocus) {
+ // Standard Android full-screen functionality.
+ getWindow().getDecorView().setSystemUiVisibility(
+ View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+ }
+
+
+ /**
+ * Create renderers after the Surface is Created and on the GL Thread
+ */
+ @Override
+ public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+
+ if (mSession == null) {
+ return;
+ }
+
+ GLES20.glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
+
+ // Create the texture and pass it to ARCore session to be filled during update().
+ mBackgroundRenderer.createOnGlThread(/*context=*/this);
+
+ try {
+
+ mSession.setCameraTextureName(mBackgroundRenderer.getTextureId());
+ mLineShaderRenderer.createOnGlThread(this);
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+
+ @Override
+ public void onSurfaceChanged(GL10 gl, int width, int height) {
+ GLES20.glViewport(0, 0, width, height);
+ // Notify ARCore session that the view size changed so that the perspective matrix and
+ // the video background can be properly adjusted.
+ mDisplayRotationHelper.onSurfaceChanged(width, height);
+ mScreenWidth = width;
+ mScreenHeight = height;
+ }
+
+
+ /**
+ * update() is executed on the GL Thread.
+ * The method handles all operations that need to take place before drawing to the screen.
+ * The method :
+ * extracts the current projection matrix and view matrix from the AR Pose
+ * handles adding stroke and points to the data collections
+ * updates the ZeroMatrix and performs the matrix multiplication needed to re-center the drawing
+ * updates the Line Renderer with the current strokes, color, distance scale, line width etc
+ */
+ private void update() {
+
+ if (mSession == null) {
+ return;
+ }
+
+ mDisplayRotationHelper.updateSessionIfNeeded(mSession);
+
+ try {
+
+ mSession.setCameraTextureName(mBackgroundRenderer.getTextureId());
+
+ mFrame = mSession.update();
+ Camera camera = mFrame.getCamera();
+
+ mState = camera.getTrackingState();
+
+ // Update tracking states
+ if (mState == TrackingState.TRACKING && !bIsTracking.get()) {
+ bIsTracking.set(true);
+ } else if (mState== TrackingState.STOPPED && bIsTracking.get()) {
+ bIsTracking.set(false);
+ bTouchDown.set(false);
+ }
+
+ // Get projection matrix.
+ camera.getProjectionMatrix(projmtx, 0, AppSettings.getNearClip(), AppSettings.getFarClip());
+ camera.getViewMatrix(viewmtx, 0);
+
+ float[] position = new float[3];
+ camera.getPose().getTranslation(position, 0);
+
+ // Check if camera has moved much, if thats the case, stop touchDown events
+ // (stop drawing lines abruptly through the air)
+ if (mLastFramePosition != null) {
+ Vector3f distance = new Vector3f(position[0], position[1], position[2]);
+ distance.sub(new Vector3f(mLastFramePosition[0], mLastFramePosition[1], mLastFramePosition[2]));
+
+ if (distance.length() > 0.15) {
+ bTouchDown.set(false);
+ }
+ }
+ mLastFramePosition = position;
+
+ // Multiply the zero matrix
+ Matrix.multiplyMM(viewmtx, 0, viewmtx, 0, mZeroMatrix, 0);
+
+
+ if (bNewStroke.get()) {
+ bNewStroke.set(false);
+ addStroke(lastTouch.get());
+ mLineShaderRenderer.bNeedsUpdate.set(true);
+ } else if (bTouchDown.get()) {
+ addPoint(lastTouch.get());
+ mLineShaderRenderer.bNeedsUpdate.set(true);
+ }
+
+ if (bReCenterView.get()) {
+ bReCenterView.set(false);
+ mZeroMatrix = getCalibrationMatrix();
+ }
+
+ if (bClearDrawing.get()) {
+ bClearDrawing.set(false);
+ clearDrawing();
+ mLineShaderRenderer.bNeedsUpdate.set(true);
+ }
+
+ if (bUndo.get()) {
+ bUndo.set(false);
+ if (mStrokes.size() > 0) {
+ mStrokes.remove(mStrokes.size() - 1);
+ mLineShaderRenderer.bNeedsUpdate.set(true);
+ }
+ }
+ mLineShaderRenderer.setDrawDebug(bLineParameters.get());
+ if (mLineShaderRenderer.bNeedsUpdate.get()) {
+ mLineShaderRenderer.setColor(AppSettings.getColor());
+ mLineShaderRenderer.mDrawDistance = AppSettings.getStrokeDrawDistance();
+ mLineShaderRenderer.setDistanceScale(mDistanceScale);
+ mLineShaderRenderer.setLineWidth(mLineWidthMax);
+ mLineShaderRenderer.clear();
+ mLineShaderRenderer.updateStrokes(mStrokes);
+ mLineShaderRenderer.upload();
+ }
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+
+
+
+ /**
+ * GL Thread Loop
+ * clears the Color Buffer and Depth Buffer, draws the current texture from the camera
+ * and draws the Line Renderer if ARCore is tracking the world around it
+ */
+ @Override
+ public void onDrawFrame(GL10 gl) {
+ if (mPaused) return;
+
+ update();
+
+ GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
+
+ if (mFrame == null) {
+ return;
+ }
+
+ // Draw background.
+ mBackgroundRenderer.draw(mFrame);
+
+ // Draw Lines
+ if (mFrame.getCamera().getTrackingState() == TrackingState.TRACKING) {
+ mLineShaderRenderer.draw(viewmtx, projmtx, mScreenWidth, mScreenHeight, AppSettings.getNearClip(), AppSettings.getFarClip());
+ }
+ }
+
+
+ /**
+ * Get a matrix usable for zero calibration (only position and compass direction)
+ */
+ public float[] getCalibrationMatrix() {
+ float[] t = new float[3];
+ float[] m = new float[16];
+
+ mFrame.getCamera().getPose().getTranslation(t, 0);
+ float[] z = mFrame.getCamera().getPose().getZAxis();
+ Vector3f zAxis = new Vector3f(z[0], z[1], z[2]);
+ zAxis.y = 0;
+ zAxis.normalize();
+
+ double rotate = Math.atan2(zAxis.x, zAxis.z);
+
+ Matrix.setIdentityM(m, 0);
+ Matrix.translateM(m, 0, t[0], t[1], t[2]);
+ Matrix.rotateM(m, 0, (float) Math.toDegrees(rotate), 0, 1, 0);
+ return m;
+ }
+
+
+ /**
+ * Clears the Datacollection of Strokes and sets the Line Renderer to clear and update itself
+ * Designed to be executed on the GL Thread
+ */
+ public void clearDrawing() {
+ mStrokes.clear();
+ mLineShaderRenderer.clear();
+ }
+
+
+ /**
+ * onClickUndo handles the touch input on the GUI and sets the AtomicBoolean bUndo to be true
+ * the actual undo functionality is executed in the GL Thread
+ */
+ public void onClickUndo(View button) {
+ bUndo.set(true);
+ }
+
+ /**
+ * onClickLineDebug toggles the Line Renderer's Debug View on and off. The line renderer will
+ * highlight the lines on the same depth plane to allow users to draw things more coherently
+ */
+ public void onClickLineDebug(View button) {
+ bLineParameters.set(!bLineParameters.get());
+ }
+
+
+ /**
+ * onClickSettings toggles showing and hiding the Line Width, Smoothing, and Debug View toggle
+ */
+ public void onClickSettings(View button) {
+ ImageButton settingsButton = findViewById(R.id.settingsButton);
+
+ if (mSettingsUI.getVisibility() == View.GONE) {
+ mSettingsUI.setVisibility(View.VISIBLE);
+ mLineDistanceScaleBar = findViewById(R.id.distanceScale);
+ mLineWidthBar = findViewById(R.id.lineWidth);
+
+ settingsButton.setColorFilter(getResources().getColor(R.color.active));
+ } else {
+ mSettingsUI.setVisibility(View.GONE);
+ settingsButton.setColorFilter(getResources().getColor(R.color.gray));
+ }
+ }
+
+ /**
+ * onClickClear handle showing an AlertDialog to clear the drawing
+ */
+ public void onClickClear(View button) {
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(this)
+ .setMessage("Sure you want to clear?");
+
+ // Set up the buttons
+ builder.setPositiveButton("Clear ", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ bClearDrawing.set(true);
+ }
+ });
+ builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.cancel();
+ }
+ });
+
+ builder.show();
+ }
+
+
+ /**
+ * onClickRecenter handles the touch input on the GUI and sets the AtomicBoolean bReCEnterView to be true
+ * the actual recenter functionality is executed on the GL Thread
+ */
+ public void onClickRecenter(View button) {
+ bReCenterView.set(true);
+ }
+
+ // ------- Touch events
+
+ /**
+ * onTouchEvent handles saving the lastTouch screen position and setting bTouchDown and bNewStroke
+ * AtomicBooleans to trigger addPoint and addStroke on the GL Thread to be called
+ */
+ @Override
+ public boolean onTouchEvent(MotionEvent tap) {
+ this.mDetector.onTouchEvent(tap);
+
+ if (tap.getAction() == MotionEvent.ACTION_DOWN ) {
+ lastTouch.set(new Vector2f(tap.getX(), tap.getY()));
+ bTouchDown.set(true);
+ bNewStroke.set(true);
+ return true;
+ } else if (tap.getAction() == MotionEvent.ACTION_MOVE || tap.getAction() == MotionEvent.ACTION_POINTER_DOWN) {
+ lastTouch.set(new Vector2f(tap.getX(), tap.getY()));
+ bTouchDown.set(true);
+ return true;
+ } else if (tap.getAction() == MotionEvent.ACTION_UP || tap.getAction() == MotionEvent.ACTION_CANCEL) {
+ bTouchDown.set(false);
+ lastTouch.set(new Vector2f(tap.getX(), tap.getY()));
+ return true;
+ }
+
+ return super.onTouchEvent(tap);
+ }
+
+
+ @Override
+ public boolean onSingleTapConfirmed(MotionEvent e) {
+ return false;
+ }
+
+ /**
+ * onDoubleTap shows and hides the Button Bar at the Top of the View
+ */
+ @Override
+ public boolean onDoubleTap(MotionEvent e) {
+ if (mButtonBar.getVisibility() == View.GONE) {
+ mButtonBar.setVisibility(View.VISIBLE);
+ } else {
+ mButtonBar.setVisibility(View.GONE);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onDoubleTapEvent(MotionEvent e) {
+ return false;
+ }
+
+ @Override
+ public boolean onDown(MotionEvent e) {
+ return false;
+ }
+
+ @Override
+ public void onShowPress(MotionEvent tap) {
+ }
+
+ @Override
+ public boolean onSingleTapUp(MotionEvent e) {
+ return false;
+ }
+
+ @Override
+ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+ return false;
+ }
+
+ @Override
+ public void onLongPress(MotionEvent e) {
+
+ }
+
+ @Override
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+ return false;
+ }
+
+}
diff --git a/app/src/main/java/drawar/PermissionHelper.java b/app/src/main/java/drawar/PermissionHelper.java
new file mode 100755
index 0000000..f234556
--- /dev/null
+++ b/app/src/main/java/drawar/PermissionHelper.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package drawar;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.pm.PackageManager;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.content.ContextCompat;
+
+/**
+ * Helper to ask camera permission.
+ */
+public class PermissionHelper {
+ private static final String CAMERA_PERMISSION = Manifest.permission.CAMERA;
+ private static final int CAMERA_PERMISSION_CODE = 0;
+
+ /**
+ * Check to see we have the necessary permissions for this app.
+ */
+ public static boolean hasCameraPermission(Activity activity) {
+ return ContextCompat.checkSelfPermission(activity, CAMERA_PERMISSION) ==
+ PackageManager.PERMISSION_GRANTED;
+
+ }
+
+ /**
+ * Check to see we have the necessary permissions for this app, and ask for them if we don't.
+ */
+ public static void requestCameraPermission(Activity activity) {
+ ActivityCompat.requestPermissions(activity, new String[]{CAMERA_PERMISSION},
+ CAMERA_PERMISSION_CODE);
+ }
+}
diff --git a/app/src/main/java/drawar/package-info.java b/app/src/main/java/drawar/package-info.java
new file mode 100755
index 0000000..7fee246
--- /dev/null
+++ b/app/src/main/java/drawar/package-info.java
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package drawar;
diff --git a/app/src/main/java/drawar/rendering/BackgroundRenderer.java b/app/src/main/java/drawar/rendering/BackgroundRenderer.java
new file mode 100755
index 0000000..b8e07ba
--- /dev/null
+++ b/app/src/main/java/drawar/rendering/BackgroundRenderer.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package drawar.rendering;
+
+import android.content.Context;
+import android.opengl.GLES11Ext;
+import android.opengl.GLES20;
+import android.opengl.GLSurfaceView;
+
+import com.google.ar.core.Frame;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+import fr.geolabs.dev.mapmint4me.R;
+
+/**
+ * This class renders the AR background from camera feed. It creates and hosts the texture
+ * given to ARCore to be filled with the camera image.
+ */
+public class BackgroundRenderer {
+ private static final String TAG = BackgroundRenderer.class.getSimpleName();
+
+ private static final int COORDS_PER_VERTEX = 3;
+ private static final int TEXCOORDS_PER_VERTEX = 2;
+ private static final int FLOAT_SIZE = 4;
+
+ private FloatBuffer mQuadVertices;
+ private FloatBuffer mQuadTexCoord;
+ private FloatBuffer mQuadTexCoordTransformed;
+
+ private int mQuadProgram;
+
+ private int mQuadPositionParam;
+ private int mQuadTexCoordParam;
+ private int mTextureId = -1;
+ private int mTextureTarget = GLES11Ext.GL_TEXTURE_EXTERNAL_OES;
+ public BackgroundRenderer() {
+ }
+
+ /**
+ * @return
+ */
+ public int getTextureId() {
+ return mTextureId;
+ }
+
+ /**
+ * Allocates and initializes OpenGL resources needed by the background renderer. Must be
+ * called on the OpenGL thread, typically in
+ * {@link GLSurfaceView.Renderer#onSurfaceCreated(GL10, EGLConfig)}.
+ *
+ * @param context Needed to access shader source.
+ */
+ public void createOnGlThread(Context context) {
+ // Generate the background texture.
+ int textures[] = new int[1];
+ GLES20.glGenTextures(1, textures, 0);
+ mTextureId = textures[0];
+ GLES20.glBindTexture(mTextureTarget, mTextureId);
+ GLES20.glTexParameteri(mTextureTarget, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
+ GLES20.glTexParameteri(mTextureTarget, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
+ GLES20.glTexParameteri(mTextureTarget, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
+ GLES20.glTexParameteri(mTextureTarget, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);
+
+ int numVertices = 4;
+ if (numVertices != QUAD_COORDS.length / COORDS_PER_VERTEX) {
+ throw new RuntimeException("Unexpected number of vertices in BackgroundRenderer.");
+ }
+
+ ByteBuffer bbVertices = ByteBuffer.allocateDirect(QUAD_COORDS.length * FLOAT_SIZE);
+ bbVertices.order(ByteOrder.nativeOrder());
+ mQuadVertices = bbVertices.asFloatBuffer();
+ mQuadVertices.put(QUAD_COORDS);
+ mQuadVertices.position(0);
+
+ ByteBuffer bbTexCoords = ByteBuffer.allocateDirect(
+ numVertices * TEXCOORDS_PER_VERTEX * FLOAT_SIZE);
+ bbTexCoords.order(ByteOrder.nativeOrder());
+ mQuadTexCoord = bbTexCoords.asFloatBuffer();
+ mQuadTexCoord.put(QUAD_TEXCOORDS);
+ mQuadTexCoord.position(0);
+
+ ByteBuffer bbTexCoordsTransformed = ByteBuffer.allocateDirect(
+ numVertices * TEXCOORDS_PER_VERTEX * FLOAT_SIZE);
+ bbTexCoordsTransformed.order(ByteOrder.nativeOrder());
+ mQuadTexCoordTransformed = bbTexCoordsTransformed.asFloatBuffer();
+
+ int vertexShader = ShaderUtil.loadGLShader(TAG, context,
+ GLES20.GL_VERTEX_SHADER, R.raw.screenquad_vertex);
+ int fragmentShader = ShaderUtil.loadGLShader(TAG, context,
+ GLES20.GL_FRAGMENT_SHADER, R.raw.screenquad_fragment_oes);
+
+ mQuadProgram = GLES20.glCreateProgram();
+ GLES20.glAttachShader(mQuadProgram, vertexShader);
+ GLES20.glAttachShader(mQuadProgram, fragmentShader);
+ GLES20.glLinkProgram(mQuadProgram);
+ GLES20.glUseProgram(mQuadProgram);
+
+ ShaderUtil.checkGLError(TAG, "Program creation");
+
+ mQuadPositionParam = GLES20.glGetAttribLocation(mQuadProgram, "a_Position");
+ mQuadTexCoordParam = GLES20.glGetAttribLocation(mQuadProgram, "a_TexCoord");
+
+ ShaderUtil.checkGLError(TAG, "Program parameters");
+ }
+
+
+ public void draw(Frame frame) {
+
+ if (frame == null) {
+ return;
+ }
+
+ // If display rotation changed (also includes view size change), we need to re-query the uv
+ // coordinates for the screen rect, as they may have changed as well.
+ if (frame.hasDisplayGeometryChanged()) {
+ frame.transformDisplayUvCoords(mQuadTexCoord, mQuadTexCoordTransformed);
+ }
+
+ // No need to test or write depth, the screen quad has arbitrary depth, and is expected
+ // to be drawn first.
+ GLES20.glDisable(GLES20.GL_DEPTH_TEST);
+ GLES20.glDepthMask(false);
+
+ GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureId);
+
+ GLES20.glUseProgram(mQuadProgram);
+
+ // Set the vertex positions.
+ GLES20.glVertexAttribPointer(
+ mQuadPositionParam, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, 0, mQuadVertices);
+
+ // Set the texture coordinates.
+ GLES20.glVertexAttribPointer(mQuadTexCoordParam, TEXCOORDS_PER_VERTEX,
+ GLES20.GL_FLOAT, false, 0, mQuadTexCoordTransformed);
+
+ // Enable vertex arrays
+ GLES20.glEnableVertexAttribArray(mQuadPositionParam);
+ GLES20.glEnableVertexAttribArray(mQuadTexCoordParam);
+
+ GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
+
+ // Disable vertex arrays
+ GLES20.glDisableVertexAttribArray(mQuadPositionParam);
+ GLES20.glDisableVertexAttribArray(mQuadTexCoordParam);
+
+ // Restore the depth state for further drawing.
+ GLES20.glDepthMask(true);
+ GLES20.glEnable(GLES20.GL_DEPTH_TEST);
+
+ ShaderUtil.checkGLError(TAG, "Draw");
+ }
+
+ public static final float[] QUAD_COORDS = new float[]{
+ -1.0f, -1.0f, 0.0f,
+ -1.0f, +1.0f, 0.0f,
+ +1.0f, -1.0f, 0.0f,
+ +1.0f, +1.0f, 0.0f,
+ };
+
+ public static final float[] QUAD_TEXCOORDS = new float[]{
+ 0.0f, 1.0f,
+ 0.0f, 0.0f,
+ 1.0f, 1.0f,
+ 1.0f, 0.0f,
+ };
+}
diff --git a/app/src/main/java/drawar/rendering/LineShaderRenderer.java b/app/src/main/java/drawar/rendering/LineShaderRenderer.java
new file mode 100644
index 0000000..f4dddfe
--- /dev/null
+++ b/app/src/main/java/drawar/rendering/LineShaderRenderer.java
@@ -0,0 +1,537 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package drawar.rendering;
+
+
+import android.content.Context;
+import android.opengl.GLES20;
+import android.opengl.GLSurfaceView;
+import android.opengl.Matrix;
+import android.util.Log;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+import javax.vecmath.Vector3f;
+
+import fr.geolabs.dev.mapmint4me.R;
+
+
+/**
+ * Renders a point cloud.
+ */
+public class LineShaderRenderer {
+
+
+ private static final String TAG = LineShaderRenderer.class.getSimpleName();
+ private static final int FLOATS_PER_POINT = 3; // X,Y,Z.
+ private static final int BYTES_PER_FLOAT = 4;
+ private static final int BYTES_PER_POINT = BYTES_PER_FLOAT * FLOATS_PER_POINT;
+
+ private float[] mModelMatrix = new float[16];
+ private float[] mModelViewMatrix = new float[16];
+ private float[] mModelViewProjectionMatrix = new float[16];
+
+
+ private int mPositionAttribute = 0;
+ private int mPreviousAttribute = 0;
+ private int mNextAttribute = 0;
+ private int mSideAttribute = 0;
+ private int mWidthAttribte = 0;
+
+ private int mCountersAttribute = 0;
+
+ private int mProjectionUniform = 0;
+ private int mModelViewUniform = 0;
+ private int mResolutionUniform = 0;
+ private int mLineWidthUniform = 0;
+ private int mColorUniform = 0;
+ private int mOpacityUniform = 0;
+ private int mNearUniform = 0;
+ private int mFarUniform = 0;
+ private int mSizeAttenuationUniform = 0;
+ private int mDrawModeUniform = 0;
+ private int mNearCutoffUniform = 0;
+ private int mFarCutoffUniform = 0;
+
+ private boolean mDrawMode = false;
+
+ private int mVisibility = 0;
+ private int mAlphaTest = 0;
+
+
+ private float[] mPositions;
+ private float[] mCounters;
+ private float[] mNext;
+ private float[] mSide;
+ private float[] mWidth;
+ private float[] mPrevious;
+
+ private int mPositionAddress;
+ private int mPreviousAddress;
+ private int mNextAddress;
+ private int mSideAddress;
+ private int mWidthAddress;
+ private int mCounterAddress;
+
+
+ private int mNumPoints = 0;
+ private int mNumBytes = 0;
+
+ private int mVbo = 0;
+ private int mVboSize = 0;
+
+ private int mProgramName = 0;
+ private float lineWidth = 0;
+
+
+ private Vector3f mColor;
+
+
+ public AtomicBoolean bNeedsUpdate = new AtomicBoolean();
+
+ private int mLineDepthScaleUniform;
+ private float mLineDepthScale = 10.0f;
+
+ public float mDrawDistance;
+
+ public LineShaderRenderer() {
+
+ }
+
+ /**
+ * Allocates and initializes OpenGL resources needed by the Line renderer. Must be
+ * called on the OpenGL thread, typically in
+ * {@link GLSurfaceView.Renderer#onSurfaceCreated(GL10, EGLConfig)}.
+ *
+ * @param context Needed to access shader source.
+ */
+ public void createOnGlThread(Context context) {
+ ShaderUtil.checkGLError(TAG, "before create");
+
+ int buffers[] = new int[1];
+ GLES20.glGenBuffers(1, buffers, 0);
+ mVbo = buffers[0];
+ GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mVbo);
+ mVboSize = 0;
+ GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, mVboSize, null, GLES20.GL_DYNAMIC_DRAW);
+ GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
+
+
+ ShaderUtil.checkGLError(TAG, "buffer alloc");
+
+
+ /**
+ *
+ * The LineShaderRenderer uses an ES2 pipeline. It uses the line_vert.glsl and
+ * line_frag.glsl shader to render a volumetric line. It uses several techniques detailed in
+ * the following resources:
+ *
+ * Drawing Lines is Hard by Matt DesLauriers
+ * https://mattdesl.svbtle.com/drawing-lines-is-hard
+ *
+ * InkSpace an Android Experiment by Zach Lieberman
+ * https://experiments.withgoogle.com/android/ink-space
+ * https://github.com/ofZach/inkSpace
+ *
+ * THREEJS.MeshLine by Jaume Sanchez
+ * https://github.com/spite/THREE.MeshLine/blob/master/src/THREE.MeshLine.js
+ *
+ *
+ * The Renderer batches all of the geometry into a single VBO. This allows us to have a single
+ * draw call to render the geometry. We also optimize the application to only re-upload the
+ * geometry data when a new stroke or new points are added to the drawing. The renderer uses
+ * a technique detailed in the following link to create degenerate faces between the strokes
+ * to disconnect them from one another.
+ * https://developer.apple.com/library/content/documentation/3DDrawing/Conceptual/OpenGLES_ProgrammingGuide/TechniquesforWorkingwithVertexData/TechniquesforWorkingwithVertexData.html
+ *
+ */
+
+ int vertexShader = ShaderUtil.loadGLShader(TAG, context,
+ GLES20.GL_VERTEX_SHADER, R.raw.line_vert);
+ int fragmentShader = ShaderUtil.loadGLShader(TAG, context,
+ GLES20.GL_FRAGMENT_SHADER, R.raw.line_frag);
+
+
+ mProgramName = GLES20.glCreateProgram();
+ GLES20.glAttachShader(mProgramName, vertexShader);
+ GLES20.glAttachShader(mProgramName, fragmentShader);
+ GLES20.glLinkProgram(mProgramName);
+ GLES20.glUseProgram(mProgramName);
+
+ ShaderUtil.checkGLError(TAG, "program");
+
+ mPositionAttribute = GLES20.glGetAttribLocation(mProgramName, "position");
+ mPreviousAttribute = GLES20.glGetAttribLocation(mProgramName, "previous");
+ mNextAttribute = GLES20.glGetAttribLocation(mProgramName, "next");
+ mSideAttribute = GLES20.glGetAttribLocation(mProgramName, "side");
+ mWidthAttribte = GLES20.glGetAttribLocation(mProgramName, "width");
+ mCountersAttribute = GLES20.glGetAttribLocation(mProgramName, "counters");
+ mProjectionUniform = GLES20.glGetUniformLocation(mProgramName, "projectionMatrix");
+ mModelViewUniform = GLES20.glGetUniformLocation(mProgramName, "modelViewMatrix");
+ mResolutionUniform = GLES20.glGetUniformLocation(mProgramName, "resolution");
+ mLineWidthUniform = GLES20.glGetUniformLocation(mProgramName, "lineWidth");
+ mColorUniform = GLES20.glGetUniformLocation(mProgramName, "color");
+ mOpacityUniform = GLES20.glGetUniformLocation(mProgramName, "opacity");
+ mNearUniform = GLES20.glGetUniformLocation(mProgramName, "near");
+ mFarUniform = GLES20.glGetUniformLocation(mProgramName, "far");
+ mSizeAttenuationUniform = GLES20.glGetUniformLocation(mProgramName, "sizeAttenuation");
+ mVisibility = GLES20.glGetUniformLocation(mProgramName, "visibility");
+ mAlphaTest = GLES20.glGetUniformLocation(mProgramName, "alphaTest");
+ mDrawModeUniform = GLES20.glGetUniformLocation(mProgramName, "drawMode");
+ mNearCutoffUniform = GLES20.glGetUniformLocation(mProgramName, "nearCutOff");
+ mFarCutoffUniform = GLES20.glGetUniformLocation(mProgramName, "farCutOff");
+ mLineDepthScaleUniform = GLES20.glGetUniformLocation(mProgramName, "lineDepthScale");
+
+ ShaderUtil.checkGLError(TAG, "program params");
+
+ Matrix.setIdentityM(mModelMatrix, 0);
+
+ mColor = new Vector3f(1f, 1f, 1f);
+ lineWidth = 0.5f;
+ }
+
+ /**
+ * Sets the LineWidth of the Line.
+ * Requires bNeedsUpdate.set(true) to take effect
+ * @param width
+ */
+ public void setLineWidth(float width){
+ lineWidth = width;
+ }
+
+ /**
+ * Enables or Disables the Debug View in the Fragment Shader. Debug View highlights the strokes
+ * at the same depth as the user. It allows the user to position new drawings to intersect or
+ * bypass the existing strokes
+ * @param drawDebugMode
+ */
+ public void setDrawDebug(boolean drawDebugMode){
+ mDrawMode = drawDebugMode;
+ }
+
+
+ /**
+ * This sets the color of the line by setting the color uniform in the shader.
+ * @param color a Vector3f representing R, G, B for the X, Y, Z values
+ */
+ public void setColor(Vector3f color) {
+ mColor = new Vector3f(color);
+ }
+
+
+ /**
+ * This sets a feature in the vertex shader to scale the line width based on the distance away
+ * from the current view.
+ * @param distanceScale
+ */
+ public void setDistanceScale(float distanceScale) {
+ this.mLineDepthScale = distanceScale;
+ }
+
+ /**
+ * This updates the geometry data to be rendered. It ensures the capacity of the float arrays
+ * and then calls addLine to generate the geometry.
+ * @param strokes a ArrayList of ArrayLists of Vector3fs in world space. The outer ArrayList
+ * contains the strokes, while the inner ArrayList contains the Vertex of each Line
+ */
+ public void updateStrokes(ArrayList> strokes) {
+ mNumPoints = 0;
+ for (ArrayList l : strokes) {
+ mNumPoints += l.size()*2 + 2;
+ }
+
+ ensureCapacity(mNumPoints);
+
+ int offset = 0;
+ for (ArrayList l : strokes) {
+ offset = addLine(l, offset);
+ }
+ mNumBytes = offset;
+
+ }
+
+ /**
+ * This ensures the capacity of the float arrays that hold the information bound to the Vertex
+ * Attributes needed to render the line with the Vertex and Fragment shader.
+ * @param numPoints
+ */
+ private void ensureCapacity(int numPoints){
+ int count = 1024;
+ if(mSide != null){
+ count = mSide.length;
+ }
+
+ while(count < numPoints){
+ count += 1024;
+ }
+
+ if(mSide == null || mSide.length < count) {
+ Log.i(TAG, "alloc "+count);
+ mPositions = new float[count*3];
+ mNext = new float[count*3];
+ mPrevious = new float[count*3];
+
+ mCounters = new float[count];
+ mSide = new float[count];
+ mWidth = new float[count];
+ }
+ }
+
+ /**
+ * AddLine takes in the 3D positions adds to the buffers to create the stroke and the degenerate
+ * faces needed so the lines render properly.
+ * @param line
+ * @param offset
+ * @return
+ */
+ private int addLine(List line, int offset) {
+ if (line == null || line.size() < 2)
+ return offset;
+
+
+
+
+ int lineSize = line.size();
+
+
+ int ii = offset;
+ for (int i = 0; i < lineSize; i++) {
+
+ int iGood = i;
+ if (iGood < 0) iGood = 0;
+ if (iGood >= lineSize) iGood = lineSize - 1;
+
+ int i_m_1 = (iGood - 1) < 0 ? iGood : iGood - 1;
+ int i_p_1 = (iGood + 1) > (lineSize - 1) ? iGood : iGood + 1;
+ float c = ((float) i / lineSize);
+
+
+ Vector3f current = line.get(iGood);
+ Vector3f previous = line.get(i_m_1);
+ Vector3f next = line.get(i_p_1);
+
+
+
+ if (i == 0) {
+ setMemory(ii++, current, previous, next, c, lineWidth, 1f);
+ }
+
+ setMemory(ii++, current, previous, next, c, lineWidth, 1f);
+ setMemory(ii++, current, previous, next, c, lineWidth, -1f);
+
+ if (i == lineSize - 1) {
+ setMemory(ii++, current, previous, next, c, lineWidth, -1f);
+ }
+ }
+ return ii;
+ }
+
+ /**
+ *
+ * setMemory is a helper method used to add the stroke data to the float[] buffers
+ * @param index
+ * @param pos
+ * @param prev
+ * @param next
+ * @param counter
+ * @param width
+ * @param side
+ */
+ private void setMemory(int index, Vector3f pos, Vector3f prev, Vector3f next, float counter, float width, float side){
+ mPositions[index*3] = pos.x;
+ mPositions[index*3+1] = pos.y;
+ mPositions[index*3+2] = pos.z;
+
+ mNext[index*3] = next.x;
+ mNext[index*3+1] = next.y;
+ mNext[index*3+2] = next.z;
+
+ mPrevious[index*3] = prev.x;
+ mPrevious[index*3+1] = prev.y;
+ mPrevious[index*3+2] = prev.z;
+
+ mCounters[index] = counter;
+ mSide[index] = side;
+ mWidth[index] = width;
+
+ }
+
+ /**
+ * Sets the bNeedsUpdate to true.
+ */
+ public void clear() {
+ bNeedsUpdate.set(true);
+ }
+
+
+ /**
+ * This takes the float[] and creates FloatBuffers, Binds the VBO, and upload the Attributes to
+ * correct locations with the correct offsets so the Vertex and Fragment shader can render the lines
+ */
+ public void upload() {
+ bNeedsUpdate.set(false);
+
+ FloatBuffer current = toFloatBuffer(mPositions);
+ FloatBuffer next = toFloatBuffer(mNext);
+ FloatBuffer previous = toFloatBuffer(mPrevious);
+
+ FloatBuffer side = toFloatBuffer(mSide);
+ FloatBuffer width = toFloatBuffer(mWidth);
+ FloatBuffer counter = toFloatBuffer(mCounters);
+
+
+// mNumPoints = mPositions.length;
+
+ mPositionAddress = 0;
+ mNextAddress = mPositionAddress + mNumBytes * 3 * BYTES_PER_FLOAT;
+ mPreviousAddress = mNextAddress + mNumBytes * 3 * BYTES_PER_FLOAT;
+ mSideAddress = mPreviousAddress + mNumBytes *3 * BYTES_PER_FLOAT;
+
+ mWidthAddress = mSideAddress + mNumBytes * BYTES_PER_FLOAT;
+ mCounterAddress = mWidthAddress + mNumBytes * BYTES_PER_FLOAT;
+ mVboSize = mCounterAddress + mNumBytes * BYTES_PER_FLOAT;
+
+ ShaderUtil.checkGLError(TAG, "before update");
+
+ GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mVbo);
+
+ GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, mVboSize, null, GLES20.GL_DYNAMIC_DRAW);
+
+ GLES20.glBufferSubData(GLES20.GL_ARRAY_BUFFER, mPositionAddress, mNumBytes * 3 * BYTES_PER_FLOAT,
+ current);
+ GLES20.glBufferSubData(GLES20.GL_ARRAY_BUFFER, mNextAddress, mNumBytes * 3 * BYTES_PER_FLOAT,
+ next);
+ GLES20.glBufferSubData(GLES20.GL_ARRAY_BUFFER, mPreviousAddress, mNumBytes * 3 * BYTES_PER_FLOAT,
+ previous);
+ GLES20.glBufferSubData(GLES20.GL_ARRAY_BUFFER, mSideAddress, mNumBytes * BYTES_PER_FLOAT,
+ side);
+ GLES20.glBufferSubData(GLES20.GL_ARRAY_BUFFER, mWidthAddress, mNumBytes * BYTES_PER_FLOAT,
+ width);
+ GLES20.glBufferSubData(GLES20.GL_ARRAY_BUFFER, mCounterAddress, mNumBytes * BYTES_PER_FLOAT,
+ counter);
+
+
+ GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
+
+ ShaderUtil.checkGLError(TAG, "after update");
+ }
+
+
+ /**
+ *
+ * This method takes in the current CameraView Matrix and the Camera's Projection Matrix, the
+ * current position and pose of the device, uses those to calculate the ModelViewMatrix and
+ * ModelViewProjectionMatrix. It binds the VBO, enables the custom attribute locations,
+ * binds and uploads the shader uniforms, calls our single DrawArray call, and finally disables
+ * and unbinds the shader attributes and VBO.
+ *
+ * @param cameraView
+ * @param cameraPerspective
+ * @param screenWidth
+ * @param screenHeight
+ * @param nearClip
+ * @param farClip
+ */
+ public void draw(float[] cameraView, float[] cameraPerspective, float screenWidth, float screenHeight, float nearClip, float farClip) {
+
+
+ Matrix.multiplyMM(mModelViewMatrix, 0, cameraView, 0, mModelMatrix, 0);
+ Matrix.multiplyMM(mModelViewProjectionMatrix, 0, cameraPerspective, 0, mModelViewMatrix, 0);
+
+ ShaderUtil.checkGLError(TAG, "Before draw");
+
+ GLES20.glUseProgram(mProgramName);
+ GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mVbo);
+ GLES20.glVertexAttribPointer(
+ mPositionAttribute, FLOATS_PER_POINT, GLES20.GL_FLOAT, false, BYTES_PER_POINT, mPositionAddress);
+ GLES20.glVertexAttribPointer(
+ mPreviousAttribute, FLOATS_PER_POINT, GLES20.GL_FLOAT, false, BYTES_PER_POINT, mPreviousAddress);
+ GLES20.glVertexAttribPointer(
+ mNextAttribute, FLOATS_PER_POINT, GLES20.GL_FLOAT, false, BYTES_PER_POINT, mNextAddress);
+ GLES20.glVertexAttribPointer(
+ mSideAttribute, 1, GLES20.GL_FLOAT, false, BYTES_PER_FLOAT, mSideAddress);
+ GLES20.glVertexAttribPointer(
+ mWidthAttribte, 1, GLES20.GL_FLOAT, false, BYTES_PER_FLOAT, mWidthAddress);
+ GLES20.glVertexAttribPointer(
+ mCountersAttribute, 1, GLES20.GL_FLOAT, false, BYTES_PER_FLOAT, mCounterAddress);
+ GLES20.glUniformMatrix4fv(
+ mModelViewUniform, 1, false, mModelViewMatrix, 0);
+ GLES20.glUniformMatrix4fv(
+ mProjectionUniform, 1, false, cameraPerspective, 0);
+
+
+ GLES20.glUniform2f(mResolutionUniform, screenWidth, screenHeight);
+ GLES20.glUniform1f(mLineWidthUniform, 0.01f);
+ GLES20.glUniform3f(mColorUniform, mColor.x, mColor.y, mColor.z);
+ GLES20.glUniform1f(mOpacityUniform, 1.0f);
+ GLES20.glUniform1f(mNearUniform, nearClip);
+ GLES20.glUniform1f(mFarUniform, farClip);
+ GLES20.glUniform1f(mSizeAttenuationUniform, 1.0f);
+ GLES20.glUniform1f(mVisibility, 1.0f);
+ GLES20.glUniform1f(mAlphaTest, 1.0f);
+ GLES20.glUniform1f(mDrawModeUniform, mDrawMode?1.0f:0.0f);
+ GLES20.glUniform1f(mNearCutoffUniform, mDrawDistance - 0.0075f);
+ GLES20.glUniform1f(mFarCutoffUniform, mDrawDistance + 0.0075f);
+ GLES20.glUniform1f(mLineDepthScaleUniform, mLineDepthScale);
+
+ GLES20.glEnableVertexAttribArray(mPositionAttribute);
+ GLES20.glEnableVertexAttribArray(mPreviousAttribute);
+ GLES20.glEnableVertexAttribArray(mNextAttribute);
+ GLES20.glEnableVertexAttribArray(mSideAttribute);
+ GLES20.glEnableVertexAttribArray(mWidthAttribte);
+ GLES20.glEnableVertexAttribArray(mCountersAttribute);
+
+
+ GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, mNumBytes);
+
+
+ GLES20.glDisableVertexAttribArray(mCountersAttribute);
+ GLES20.glDisableVertexAttribArray(mWidthAttribte);
+ GLES20.glDisableVertexAttribArray(mSideAttribute);
+ GLES20.glDisableVertexAttribArray(mNextAttribute);
+ GLES20.glDisableVertexAttribArray(mPreviousAttribute);
+ GLES20.glDisableVertexAttribArray(mPositionAttribute);
+
+
+ GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
+
+ ShaderUtil.checkGLError(TAG, "Draw");
+ }
+
+
+ /**
+ * A helper function to allocate a FloatBuffer the size of our float[] and copy the float[] into
+ * the newly created FloatBuffer.
+ * @param data
+ * @return
+ */
+ private FloatBuffer toFloatBuffer(float[] data) {
+ FloatBuffer buff;
+ ByteBuffer bb = ByteBuffer.allocateDirect(data.length * BYTES_PER_FLOAT);
+ bb.order(ByteOrder.nativeOrder());
+ buff = bb.asFloatBuffer();
+ buff.put(data);
+ buff.position(0);
+ return buff;
+ }
+
+}
diff --git a/app/src/main/java/drawar/rendering/LineUtils.java b/app/src/main/java/drawar/rendering/LineUtils.java
new file mode 100644
index 0000000..614c763
--- /dev/null
+++ b/app/src/main/java/drawar/rendering/LineUtils.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ package drawar.rendering;
+
+import android.opengl.Matrix;
+
+import javax.vecmath.Vector2f;
+import javax.vecmath.Vector3f;
+
+import drawar.AppSettings;
+
+public class LineUtils {
+
+ /**
+ * @param value
+ * @param inputMin
+ * @param inputMax
+ * @param outputMin
+ * @param outputMax
+ * @param clamp
+ * @return
+ */
+ public static float map(float value, float inputMin, float inputMax, float outputMin, float outputMax, boolean clamp) {
+ float outVal = ((value - inputMin) / (inputMax - inputMin) * (outputMax - outputMin) + outputMin);
+
+ if (clamp) {
+ if (outputMax < outputMin) {
+ if (outVal < outputMax) outVal = outputMax;
+ else if (outVal > outputMin) outVal = outputMin;
+ } else {
+ if (outVal > outputMax) outVal = outputMax;
+ else if (outVal < outputMin) outVal = outputMin;
+ }
+ }
+ return outVal;
+ }
+
+ /**
+ * @param start
+ * @param stop
+ * @param amt
+ * @return
+ */
+ public static float lerp(float start, float stop, float amt) {
+ return start + (stop - start) * amt;
+ }
+
+ /**
+ * @param touchPoint
+ * @param screenWidth
+ * @param screenHeight
+ * @param projectionMatrix
+ * @param viewMatrix
+ * @return
+ */
+ public static Vector3f GetWorldCoords(Vector2f touchPoint, float screenWidth, float screenHeight, float[] projectionMatrix, float[] viewMatrix) {
+ Ray touchRay = projectRay(touchPoint, screenWidth, screenHeight, projectionMatrix, viewMatrix);
+ touchRay.direction.scale(AppSettings.getStrokeDrawDistance());
+ touchRay.origin.add(touchRay.direction);
+ return touchRay.origin;
+ }
+
+ /**
+ * @param point
+ * @param viewportSize
+ * @param viewProjMtx
+ * @return
+ */
+ public static Ray screenPointToRay(Vector2f point, Vector2f viewportSize, float[] viewProjMtx) {
+ point.y = viewportSize.y - point.y;
+ float x = point.x * 2.0F / viewportSize.x - 1.0F;
+ float y = point.y * 2.0F / viewportSize.y - 1.0F;
+ float[] farScreenPoint = new float[]{x, y, 1.0F, 1.0F};
+ float[] nearScreenPoint = new float[]{x, y, -1.0F, 1.0F};
+ float[] nearPlanePoint = new float[4];
+ float[] farPlanePoint = new float[4];
+ float[] invertedProjectionMatrix = new float[16];
+ Matrix.setIdentityM(invertedProjectionMatrix, 0);
+ Matrix.invertM(invertedProjectionMatrix, 0, viewProjMtx, 0);
+ Matrix.multiplyMV(nearPlanePoint, 0, invertedProjectionMatrix, 0, nearScreenPoint, 0);
+ Matrix.multiplyMV(farPlanePoint, 0, invertedProjectionMatrix, 0, farScreenPoint, 0);
+ Vector3f direction = new Vector3f(farPlanePoint[0] / farPlanePoint[3], farPlanePoint[1] / farPlanePoint[3], farPlanePoint[2] / farPlanePoint[3]);
+ Vector3f origin = new Vector3f(new Vector3f(nearPlanePoint[0] / nearPlanePoint[3], nearPlanePoint[1] / nearPlanePoint[3], nearPlanePoint[2] / nearPlanePoint[3]));
+ direction.sub(origin);
+ direction.normalize();
+ return new Ray(origin, direction);
+ }
+
+ /**
+ * @param touchPoint
+ * @param screenWidth
+ * @param screenHeight
+ * @param projectionMatrix
+ * @param viewMatrix
+ * @return
+ */
+ public static Ray projectRay(Vector2f touchPoint, float screenWidth, float screenHeight, float[] projectionMatrix, float[] viewMatrix) {
+ float[] viewProjMtx = new float[16];
+ Matrix.multiplyMM(viewProjMtx, 0, projectionMatrix, 0, viewMatrix, 0);
+ return screenPointToRay(touchPoint, new Vector2f(screenWidth, screenHeight), viewProjMtx);
+ }
+
+
+ /**
+ * @param newPoint
+ * @param lastPoint
+ * @return
+ */
+ public static boolean distanceCheck(Vector3f newPoint, Vector3f lastPoint) {
+ Vector3f temp = new Vector3f();
+ temp.sub(newPoint, lastPoint);
+ if (temp.length() > AppSettings.getMinDistance()) {
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/app/src/main/java/drawar/rendering/Ray.java b/app/src/main/java/drawar/rendering/Ray.java
new file mode 100644
index 0000000..223c584
--- /dev/null
+++ b/app/src/main/java/drawar/rendering/Ray.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package drawar.rendering;
+
+import javax.vecmath.Vector3f;
+
+public class Ray {
+ public final Vector3f origin;
+ public final Vector3f direction;
+ public Ray(Vector3f origin, Vector3f direction) {
+ this.origin = origin;
+ this.direction = direction;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/drawar/rendering/ShaderUtil.java b/app/src/main/java/drawar/rendering/ShaderUtil.java
new file mode 100755
index 0000000..0192731
--- /dev/null
+++ b/app/src/main/java/drawar/rendering/ShaderUtil.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package drawar.rendering;
+
+import android.content.Context;
+import android.opengl.GLES20;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+/**
+ * Shader helper functions.
+ */
+public class ShaderUtil {
+ /**
+ * Converts a raw text file, saved as a resource, into an OpenGL ES shader.
+ *
+ * @param type The type of shader we will be creating.
+ * @param resId The resource ID of the raw text file about to be turned into a shader.
+ * @return The shader object handler.
+ */
+ public static int loadGLShader(String tag, Context context, int type, int resId) {
+ String code = readRawTextFile(context, resId);
+ int shader = GLES20.glCreateShader(type);
+ GLES20.glShaderSource(shader, code);
+ GLES20.glCompileShader(shader);
+
+ // Get the compilation status.
+ final int[] compileStatus = new int[1];
+ GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compileStatus, 0);
+
+ // If the compilation failed, delete the shader.
+ if (compileStatus[0] == 0) {
+ Log.e(tag, "Error compiling shader: " + GLES20.glGetShaderInfoLog(shader));
+ GLES20.glDeleteShader(shader);
+ shader = 0;
+ }
+
+ if (shader == 0) {
+ throw new RuntimeException("Error creating shader.");
+ }
+
+ return shader;
+ }
+
+ /**
+ * Checks if we've had an error inside of OpenGL ES, and if so what that error is.
+ *
+ * @param label Label to report in case of error.
+ * @throws RuntimeException If an OpenGL error is detected.
+ */
+ public static void checkGLError(String tag, String label) {
+ int error;
+ while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
+ Log.e(tag, label + ": glError " + error);
+ throw new RuntimeException(label + ": glError " + error);
+ }
+ }
+
+ /**
+ * Converts a raw text file into a string.
+ *
+ * @param resId The resource ID of the raw text file about to be turned into a shader.
+ * @return The context of the text file, or null in case of error.
+ */
+ private static String readRawTextFile(Context context, int resId) {
+ InputStream inputStream = context.getResources().openRawResource(resId);
+ try {
+ BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
+ StringBuilder sb = new StringBuilder();
+ String line;
+ while ((line = reader.readLine()) != null) {
+ sb.append(line).append("\n");
+ }
+ reader.close();
+ return sb.toString();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+}
diff --git a/app/src/main/java/drawar/rendering/package-info.java b/app/src/main/java/drawar/rendering/package-info.java
new file mode 100755
index 0000000..08169a6
--- /dev/null
+++ b/app/src/main/java/drawar/rendering/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * This package contains classes that do the rendering for this example.
+ */
+package drawar.rendering;
diff --git a/app/src/main/java/fr/geolabs/dev/mapmint4me/ARSimulation.java b/app/src/main/java/fr/geolabs/dev/mapmint4me/ARSimulation.java
new file mode 100644
index 0000000..f9b1640
--- /dev/null
+++ b/app/src/main/java/fr/geolabs/dev/mapmint4me/ARSimulation.java
@@ -0,0 +1,6 @@
+package fr.geolabs.dev.mapmint4me;
+
+import android.app.Activity;
+
+public class ARSimulation extends Activity {
+}
diff --git a/app/src/main/java/fr/geolabs/dev/mapmint4me/CloudAnchor.java b/app/src/main/java/fr/geolabs/dev/mapmint4me/CloudAnchor.java
new file mode 100644
index 0000000..6c73007
--- /dev/null
+++ b/app/src/main/java/fr/geolabs/dev/mapmint4me/CloudAnchor.java
@@ -0,0 +1,219 @@
+package fr.geolabs.dev.mapmint4me;
+
+
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.Toast;
+
+import com.google.ar.core.Anchor;
+import com.google.ar.sceneform.AnchorNode;
+import com.google.ar.sceneform.rendering.ModelRenderable;
+import com.google.firebase.database.DataSnapshot;
+import com.google.firebase.database.DatabaseError;
+import com.google.firebase.database.DatabaseReference;
+import com.google.firebase.database.FirebaseDatabase;
+import com.google.firebase.database.ValueEventListener;
+
+public class CloudAnchor extends AppCompatActivity {
+
+ private DatabaseReference mDatabase;
+ private DatabaseReference mref;
+ private DatabaseReference mload;
+
+ EditText anchorid;
+ Button resolve;
+ private CustomArFragment arFragment;
+
+ private enum AppAnchorState {
+ NONE,
+ HOSTING,
+ HOSTED
+ }
+
+ private AppAnchorState appAnchorState = AppAnchorState.NONE;
+
+ private Anchor anchor;
+
+ private boolean isPlaced = false;
+
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.cloudanchor);
+ mDatabase = FirebaseDatabase.getInstance().getReference();
+
+ mload=mDatabase.child("lastcode");
+
+ anchorid =(EditText)findViewById(R.id.editText);
+ resolve=(Button)findViewById(R.id.resolve);
+
+
+ arFragment = (CustomArFragment) getSupportFragmentManager().findFragmentById(R.id.fragment);
+
+ arFragment.setOnTapArPlaneListener((hitResult, plane, motionEvent) -> {
+
+ if (!isPlaced) {
+
+ anchor = arFragment.getArSceneView().getSession().hostCloudAnchor(hitResult.createAnchor());
+
+ appAnchorState = AppAnchorState.HOSTING;
+
+
+ createModel(anchor);
+
+ isPlaced = true;
+
+ }
+
+
+ });
+
+
+ arFragment.getArSceneView().getScene().addOnUpdateListener(frameTime -> {
+
+ if (appAnchorState != AppAnchorState.HOSTING)
+ return;
+
+ Anchor.CloudAnchorState cloudAnchorState = anchor.getCloudAnchorState();
+
+ if (cloudAnchorState.isError()) {
+ showToast(cloudAnchorState.toString());
+ } else if (cloudAnchorState == Anchor.CloudAnchorState.SUCCESS) {
+ showToast("Hosting Anchor..... " );
+
+ appAnchorState = AppAnchorState.HOSTED;
+ String anchorId = anchor.getCloudAnchorId();
+
+
+
+
+
+ mload.addListenerForSingleValueEvent(new ValueEventListener() {
+ @Override
+ public void onDataChange(DataSnapshot dataSnapshot) {
+
+ String value = (String) dataSnapshot.getValue();
+
+
+ mDatabase.child(value).setValue(anchorId);
+
+ showToast("Anchor Hosted On Room Code : "+ value);
+
+ int i = Integer.parseInt(value);
+ i=i+1;
+ mDatabase.child("lastcode").setValue(""+i);
+
+ resolve.setEnabled(false);
+ anchorid.setEnabled(false);
+
+
+
+
+
+
+
+ }
+
+ @Override
+ public void onCancelled(DatabaseError databaseError) {
+ showToast("Firebase Error");
+
+ }
+
+
+ });
+
+
+
+
+
+ }
+
+ });
+
+ }
+
+ private void showToast(String toString) {
+ Toast.makeText(CloudAnchor.this, toString, Toast.LENGTH_SHORT).show();
+ }
+
+ private void createModel(Anchor anchor) {
+
+ ModelRenderable.builder().setSource(this, Uri.parse("model.sfb")).build()
+ .thenAccept(modelRenderable -> placeModel(anchor, modelRenderable));
+
+ }
+
+ private void placeModel(Anchor anchor, ModelRenderable modelRenderable) {
+
+ AnchorNode anchorNode = new AnchorNode(anchor);
+ anchorNode.setRenderable(modelRenderable);
+ arFragment.getArSceneView().getScene().addChild(anchorNode);
+ }
+
+ public void OnButtonClick(View v)
+ {
+ String code=anchorid.getText().toString();
+
+ mref = mDatabase.child(code);
+
+
+ mref.addListenerForSingleValueEvent(new ValueEventListener() {
+ @Override
+ public void onDataChange(DataSnapshot dataSnapshot) {
+
+ try {
+ String value = (String) dataSnapshot.getValue();
+
+ if(value.equals(""))
+ showToast("Room Code Unavalible");
+ else
+ {
+
+
+
+ showToast("Cloud Anchor Loaded:" + value);
+ Anchor resolvedAnchor = arFragment.getArSceneView().getSession().resolveCloudAnchor(value);
+ createModel(resolvedAnchor);
+
+ resolve.setEnabled(false);
+ anchorid.setEnabled(false);
+
+ }
+ }
+ catch (NullPointerException e)
+ {
+
+ showToast("Enter Valid Anchor Room Code");
+ }
+
+
+
+
+ }
+
+ @Override
+ public void onCancelled(DatabaseError databaseError) {
+ showToast("Firebase Error");
+
+ }
+
+
+ });
+
+
+
+
+ }
+
+
+
+}
+
+
+
diff --git a/app/src/main/java/fr/geolabs/dev/mapmint4me/CustomArFragment.java b/app/src/main/java/fr/geolabs/dev/mapmint4me/CustomArFragment.java
new file mode 100644
index 0000000..234b76f
--- /dev/null
+++ b/app/src/main/java/fr/geolabs/dev/mapmint4me/CustomArFragment.java
@@ -0,0 +1,24 @@
+package fr.geolabs.dev.mapmint4me;
+
+import com.google.ar.core.Config;
+import com.google.ar.core.Session;
+import com.google.ar.sceneform.ux.ArFragment;
+
+public class CustomArFragment extends ArFragment {
+ @Override
+ protected Config getSessionConfiguration(Session session) {
+
+ Config config= new Config(session);
+
+ config.setCloudAnchorMode(Config.CloudAnchorMode.ENABLED);
+ config.setFocusMode(Config.FocusMode.AUTO);
+
+ session.configure(config);
+
+ this.getArSceneView().setupSession(session);
+
+ return config;
+
+
+ }
+}
diff --git a/app/src/main/java/fr/geolabs/dev/mapmint4me/MapMint4ME.java b/app/src/main/java/fr/geolabs/dev/mapmint4me/MapMint4ME.java
index c81766d..1bc19fe 100644
--- a/app/src/main/java/fr/geolabs/dev/mapmint4me/MapMint4ME.java
+++ b/app/src/main/java/fr/geolabs/dev/mapmint4me/MapMint4ME.java
@@ -1,12 +1,10 @@
package fr.geolabs.dev.mapmint4me;
-import android.*;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.NotificationChannel;
import android.app.NotificationManager;
-import android.app.PendingIntent;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
@@ -15,8 +13,6 @@
import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
import android.hardware.GeomagneticField;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
@@ -24,42 +20,24 @@
import android.hardware.SensorManager;
import android.location.Location;
import android.location.LocationManager;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
import android.net.Uri;
-import android.net.http.SslError;
import android.os.Build;
-import android.os.Environment;
-import android.os.ParcelFileDescriptor;
+import android.os.Bundle;
+import android.os.Handler;
import android.provider.MediaStore;
-import android.provider.SyncStateContract;
import android.support.v4.app.ActivityCompat;
-import android.support.v4.app.NotificationCompat;
-import android.support.v4.app.NotificationManagerCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.FileProvider;
-import android.support.v7.app.ActionBar;
-import android.support.v7.app.AppCompatActivity;
-import android.os.Bundle;
-import android.os.Handler;
-import android.support.v7.widget.AlertDialogLayout;
-import android.text.format.DateFormat;
import android.util.Log;
-import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
-import android.view.Window;
import android.webkit.CookieManager;
-import android.webkit.CookieSyncManager;
import android.webkit.GeolocationPermissions;
import android.webkit.JavascriptInterface;
-import android.webkit.SslErrorHandler;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
import android.widget.Toast;
import com.google.android.gms.appindexing.Action;
@@ -73,7 +51,6 @@
import java.io.BufferedOutputStream;
import java.io.File;
-import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -84,12 +61,8 @@
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ScheduledThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-
-import static android.provider.MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE;
-import static java.lang.Thread.sleep;
-
+import armeasure.Armeasure_save;
/**
@@ -293,6 +266,16 @@ public boolean shouldOverrideUrlLoading(WebView webView, String webResourceReque
}
}
+
+
+
+
+
+
+
+
+
+
private String channel_name="MapMint4ME";
private String channel_description="MapMint4ME channel used for notification";
public String CHANNEL_ID="MapMint4ME-11223344";
@@ -314,10 +297,27 @@ public void createNotificationChannel() {
}
}
+
public void launchWelcomeScreen() {
startActivity(new Intent(getApplicationContext(), WelcomeScreen.class));
finish();
}
+ public void launchWelcomeScreen2() {
+ startActivity(new Intent(getApplicationContext(), com.hl3hl3.arcoremeasure.ArMeasureActivity.class));
+ finish();
+ }
+ public void launchWelcomeScreen3() {
+ startActivity(new Intent(getApplicationContext(), drawar.DrawAR.class));
+ finish();
+ } public void launchWelcomeScreen4() {
+ startActivity(new Intent(getApplicationContext(), CloudAnchor.class));
+ finish();
+ }
+
+ // public void launchWelcomeScreen5() {
+ // startActivity(new Intent(getApplicationContext(), GPSActivity_sat.class));
+ // finish();
+ // }
public boolean mInternetActivated=false;
@@ -505,6 +505,23 @@ public void onAccuracyChanged(Sensor sensor, int accuracy) {
// not in use
}
+ public void launchWelcomeScreen7() {
+ startActivity(new Intent(getApplicationContext(), Satellite.GPSActivity.class));
+ finish();
+
+
+ }
+
+ public void launchWelcomeScreen8() {
+ startActivity(new Intent(getApplicationContext(), Ar_Simulation.ARSimulation.class));
+ finish();
+
+ }
+ public void launchWelcomeScreen9() {
+ startActivity(new Intent(getApplicationContext(), armeasure.Armeasure_save.class));
+ finish();
+
+ }
/**
diff --git a/app/src/main/java/fr/geolabs/dev/mapmint4me/WebAppInterface.java b/app/src/main/java/fr/geolabs/dev/mapmint4me/WebAppInterface.java
index 398d765..c168e9f 100644
--- a/app/src/main/java/fr/geolabs/dev/mapmint4me/WebAppInterface.java
+++ b/app/src/main/java/fr/geolabs/dev/mapmint4me/WebAppInterface.java
@@ -1,10 +1,8 @@
package fr.geolabs.dev.mapmint4me;
-import android.*;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.annotation.TargetApi;
-import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
@@ -41,25 +39,19 @@
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
-import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
-import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
-import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
-import java.net.HttpCookie;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
-import java.util.Arrays;
-import java.util.List;
import java.util.Locale;
import java.util.regex.Pattern;
@@ -114,9 +106,9 @@ public void notify(String msg) {
.setContentText(msg)
.setStyle(new NotificationCompat.BigTextStyle().bigText(msg))
.setPriority(NotificationCompat.PRIORITY_DEFAULT);
- // Set the intent that will fire when the user taps the notification
- //.setContentIntent(pendingIntent);
- //.setAutoCancel(true);
+ // Set the intent that will fire when the user taps the notification
+ //.setContentIntent(pendingIntent);
+ //.setAutoCancel(true);
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(mContext);
currentId++;
@@ -441,25 +433,25 @@ public String translate(String text) {
/**
* Set mTop to true/false from the web page
- @JavascriptInterface
- public String getOrientation() throws JSONException{
- try {
- ((MapMint4ME)mContext).updateOrientationAngles();
- float[] res=((MapMint4ME)mContext).getOrientationAngles();
- Log.e("Error", "" + res.toString());
- JSONArray jsonArray = new JSONArray();
- for(int i=0;i<3;i++)
- jsonArray.put(i,res[i]);
- float[] res1=((MapMint4ME)mContext).getRotationMatrix();
- Log.e("Error", "" + res.toString());
- for(int i=0;i<3;i++)
- jsonArray.put(i+3,res1[i]);
- return jsonArray.toString();
- } catch (Exception e) {
- Log.e("Error", "" + e.toString());
- return e.toString();
- }
- }*/
+ @JavascriptInterface
+ public String getOrientation() throws JSONException{
+ try {
+ ((MapMint4ME)mContext).updateOrientationAngles();
+ float[] res=((MapMint4ME)mContext).getOrientationAngles();
+ Log.e("Error", "" + res.toString());
+ JSONArray jsonArray = new JSONArray();
+ for(int i=0;i<3;i++)
+ jsonArray.put(i,res[i]);
+ float[] res1=((MapMint4ME)mContext).getRotationMatrix();
+ Log.e("Error", "" + res.toString());
+ for(int i=0;i<3;i++)
+ jsonArray.put(i+3,res1[i]);
+ return jsonArray.toString();
+ } catch (Exception e) {
+ Log.e("Error", "" + e.toString());
+ return e.toString();
+ }
+ }*/
private LocalDB db = null;
@@ -570,11 +562,53 @@ public String downloadedFile() {
}
@JavascriptInterface
- public void startWelcomeScreen(){
- ((MapMint4ME) mContext).launchWelcomeScreen();
- ((MapMint4ME) mContext).finish();
+
+ public void startWelcomeScreen(String s) {
+
+ if (s.equals("scale")) {
+
+ ((MapMint4ME) mContext).launchWelcomeScreen2();
+ ((MapMint4ME) mContext).finish();
+ }
+ if (s.equals("draw")) {
+
+ ((MapMint4ME) mContext).launchWelcomeScreen3();
+ ((MapMint4ME) mContext).finish();
+ }
+ if (s.equals("cloud")) {
+
+ ((MapMint4ME) mContext).launchWelcomeScreen4();
+ ((MapMint4ME) mContext).finish();
+ }
+ if (s.equals("sat_finder")) {
+
+ ((MapMint4ME) mContext).launchWelcomeScreen7();
+ ((MapMint4ME) mContext).finish();
+ }
+
+ if (s.equals("ARSimulation")) {
+
+ ((MapMint4ME) mContext).launchWelcomeScreen8();
+ ((MapMint4ME) mContext).finish();
+ }
+
+ if (s.equals("ARArea")) {
+
+ ((MapMint4ME) mContext).launchWelcomeScreen9();
+ ((MapMint4ME) mContext).finish();
+ }
+
+
+
+ if (s.equals("help")) {
+ ((MapMint4ME) mContext).launchWelcomeScreen();
+ ((MapMint4ME) mContext).finish();
+ }
}
+
+
+
@JavascriptInterface
public void keepScreenOn(){
((MapMint4ME) mContext).runOnUiThread(new Runnable() {
@@ -759,7 +793,7 @@ public String getBaseLayers() throws JSONException, IOException {
public String getGNStatus() throws JSONException {
JSONObject json = new JSONObject();
ConnectivityManager connectivityManager
- = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+ = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
json.put("net",(activeNetworkInfo!=null&&activeNetworkInfo.isConnected()));
try{
diff --git a/app/src/main/java/fr/geolabs/dev/mapmint4me/WelcomeScreen.java b/app/src/main/java/fr/geolabs/dev/mapmint4me/WelcomeScreen.java
index 23e92d3..a382dcb 100644
--- a/app/src/main/java/fr/geolabs/dev/mapmint4me/WelcomeScreen.java
+++ b/app/src/main/java/fr/geolabs/dev/mapmint4me/WelcomeScreen.java
@@ -2,23 +2,19 @@
import android.annotation.SuppressLint;
import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
-import android.support.v7.app.AppCompatActivity;
-
import android.content.Context;
import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.view.PagerAdapter;
+import android.support.v4.view.ViewPager;
+import android.text.Html;
import android.util.Log;
import android.view.LayoutInflater;
-import android.view.ViewGroup;
import android.view.View;
-import android.text.Html;
+import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
-import android.os.Bundle;
-import android.support.v4.view.PagerAdapter;
-import android.support.v4.view.ViewPager;
import android.widget.LinearLayout;
import android.widget.TextView;
diff --git a/app/src/main/res/drawable/bg_btn.xml b/app/src/main/res/drawable/bg_btn.xml
new file mode 100644
index 0000000..a72158f
--- /dev/null
+++ b/app/src/main/res/drawable/bg_btn.xml
@@ -0,0 +1,10 @@
+
+
+ -
+
+
+
+
+ -
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/circle.xml b/app/src/main/res/drawable/circle.xml
new file mode 100644
index 0000000..d3ed938
--- /dev/null
+++ b/app/src/main/res/drawable/circle.xml
@@ -0,0 +1,6 @@
+
+
+
+
+ />
+
diff --git a/app/src/main/res/drawable/circle2.xml b/app/src/main/res/drawable/circle2.xml
new file mode 100644
index 0000000..3f51438
--- /dev/null
+++ b/app/src/main/res/drawable/circle2.xml
@@ -0,0 +1,6 @@
+
+
+
+
+ />
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/draw.png b/app/src/main/res/drawable/draw.png
new file mode 100755
index 0000000..374d91c
Binary files /dev/null and b/app/src/main/res/drawable/draw.png differ
diff --git a/app/src/main/res/drawable/ic_check_empty.png b/app/src/main/res/drawable/ic_check_empty.png
new file mode 100644
index 0000000..2b9db14
Binary files /dev/null and b/app/src/main/res/drawable/ic_check_empty.png differ
diff --git a/app/src/main/res/drawable/ic_check_pressed.png b/app/src/main/res/drawable/ic_check_pressed.png
new file mode 100644
index 0000000..dd38cd8
Binary files /dev/null and b/app/src/main/res/drawable/ic_check_pressed.png differ
diff --git a/app/src/main/res/drawable/ic_checked.png b/app/src/main/res/drawable/ic_checked.png
new file mode 100644
index 0000000..e3ce43f
Binary files /dev/null and b/app/src/main/res/drawable/ic_checked.png differ
diff --git a/app/src/main/res/drawable/ic_cube.xml b/app/src/main/res/drawable/ic_cube.xml
new file mode 100644
index 0000000..de64e59
--- /dev/null
+++ b/app/src/main/res/drawable/ic_cube.xml
@@ -0,0 +1,9 @@
+
+
+ -
+
+ -
+
+ -
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_cube_disable.png b/app/src/main/res/drawable/ic_cube_disable.png
new file mode 100644
index 0000000..47ded7e
Binary files /dev/null and b/app/src/main/res/drawable/ic_cube_disable.png differ
diff --git a/app/src/main/res/drawable/ic_cube_green.png b/app/src/main/res/drawable/ic_cube_green.png
new file mode 100644
index 0000000..93c637e
Binary files /dev/null and b/app/src/main/res/drawable/ic_cube_green.png differ
diff --git a/app/src/main/res/drawable/ic_cube_pink.png b/app/src/main/res/drawable/ic_cube_pink.png
new file mode 100644
index 0000000..4f0650e
Binary files /dev/null and b/app/src/main/res/drawable/ic_cube_pink.png differ
diff --git a/app/src/main/res/drawable/ic_delete_black_24dp.xml b/app/src/main/res/drawable/ic_delete_black_24dp.xml
new file mode 100644
index 0000000..39e64d6
--- /dev/null
+++ b/app/src/main/res/drawable/ic_delete_black_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_launcher.xml b/app/src/main/res/drawable/ic_launcher.xml
new file mode 100644
index 0000000..9d69828
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_my_location_black_24dp.xml b/app/src/main/res/drawable/ic_my_location_black_24dp.xml
new file mode 100644
index 0000000..07d6e46
--- /dev/null
+++ b/app/src/main/res/drawable/ic_my_location_black_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_settings_black_24dp.xml b/app/src/main/res/drawable/ic_settings_black_24dp.xml
new file mode 100644
index 0000000..ace746c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_settings_black_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_undo_black_24dp.xml b/app/src/main/res/drawable/ic_undo_black_24dp.xml
new file mode 100644
index 0000000..5558e37
--- /dev/null
+++ b/app/src/main/res/drawable/ic_undo_black_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/launch_screen.xml b/app/src/main/res/drawable/launch_screen.xml
new file mode 100644
index 0000000..f77c2eb
--- /dev/null
+++ b/app/src/main/res/drawable/launch_screen.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+ -
+
+
+
+
diff --git a/app/src/main/res/drawable/scale.jpg b/app/src/main/res/drawable/scale.jpg
new file mode 100644
index 0000000..4a77860
Binary files /dev/null and b/app/src/main/res/drawable/scale.jpg differ
diff --git a/app/src/main/res/drawable/scrollbar_vertical_thumb.xml b/app/src/main/res/drawable/scrollbar_vertical_thumb.xml
new file mode 100644
index 0000000..aef8cd2
--- /dev/null
+++ b/app/src/main/res/drawable/scrollbar_vertical_thumb.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/scrollbar_vertical_track.xml b/app/src/main/res/drawable/scrollbar_vertical_track.xml
new file mode 100644
index 0000000..e98305c
--- /dev/null
+++ b/app/src/main/res/drawable/scrollbar_vertical_track.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/selector_cb.xml b/app/src/main/res/drawable/selector_cb.xml
new file mode 100644
index 0000000..f6a7731
--- /dev/null
+++ b/app/src/main/res/drawable/selector_cb.xml
@@ -0,0 +1,9 @@
+
+
+ -
+
+ -
+
+ -
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/act_layout.xml b/app/src/main/res/layout/act_layout.xml
new file mode 100644
index 0000000..da03977
--- /dev/null
+++ b/app/src/main/res/layout/act_layout.xml
@@ -0,0 +1,142 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_gps.xml b/app/src/main/res/layout/activity_gps.xml
new file mode 100644
index 0000000..22e700d
--- /dev/null
+++ b/app/src/main/res/layout/activity_gps.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100755
index 0000000..b26d0ea
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/ar_simulation.xml b/app/src/main/res/layout/ar_simulation.xml
new file mode 100644
index 0000000..72c5690
--- /dev/null
+++ b/app/src/main/res/layout/ar_simulation.xml
@@ -0,0 +1,131 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/armeasure_save.xml b/app/src/main/res/layout/armeasure_save.xml
new file mode 100644
index 0000000..cb03bba
--- /dev/null
+++ b/app/src/main/res/layout/armeasure_save.xml
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/cloudanchor.xml b/app/src/main/res/layout/cloudanchor.xml
new file mode 100644
index 0000000..7d9bf0a
--- /dev/null
+++ b/app/src/main/res/layout/cloudanchor.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/dialog_save.xml b/app/src/main/res/layout/dialog_save.xml
new file mode 100644
index 0000000..fe410a4
--- /dev/null
+++ b/app/src/main/res/layout/dialog_save.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_gpsfix.xml b/app/src/main/res/layout/fragment_gpsfix.xml
new file mode 100644
index 0000000..bd7648c
--- /dev/null
+++ b/app/src/main/res/layout/fragment_gpsfix.xml
@@ -0,0 +1,450 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout.xml b/app/src/main/res/layout/layout.xml
new file mode 100644
index 0000000..85dc9f7
--- /dev/null
+++ b/app/src/main/res/layout/layout.xml
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/menu/main_menu.xml b/app/src/main/res/menu/main_menu.xml
new file mode 100644
index 0000000..2ea5f38
--- /dev/null
+++ b/app/src/main/res/menu/main_menu.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/raw/cart.sfb b/app/src/main/res/raw/cart.sfb
new file mode 100644
index 0000000..bd94961
Binary files /dev/null and b/app/src/main/res/raw/cart.sfb differ
diff --git a/app/src/main/res/raw/cone.sfb b/app/src/main/res/raw/cone.sfb
new file mode 100644
index 0000000..de08d53
Binary files /dev/null and b/app/src/main/res/raw/cone.sfb differ
diff --git a/app/src/main/res/raw/cubito.sfb b/app/src/main/res/raw/cubito.sfb
new file mode 100644
index 0000000..dbb2c1f
Binary files /dev/null and b/app/src/main/res/raw/cubito.sfb differ
diff --git a/app/src/main/res/raw/light_vertex.shader b/app/src/main/res/raw/light_vertex.shader
new file mode 100755
index 0000000..e93512a
--- /dev/null
+++ b/app/src/main/res/raw/light_vertex.shader
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+uniform mat4 u_Model;
+uniform mat4 u_ModelViewProjection;
+uniform mat4 u_ModelView;
+uniform vec3 u_LightPos;
+
+attribute vec4 a_Position;
+attribute vec4 a_Color;
+attribute vec3 a_Normal;
+
+varying vec4 v_Color;
+varying vec3 v_Grid;
+
+void main() {
+ v_Grid = vec3(u_Model * a_Position);
+
+ vec3 modelViewVertex = vec3(u_ModelView * a_Position);
+ vec3 modelViewNormal = vec3(u_ModelView * vec4(a_Normal, 0.0));
+
+ float distance = length(u_LightPos - modelViewVertex);
+ vec3 lightVector = normalize(u_LightPos - modelViewVertex);
+ float diffuse = max(dot(modelViewNormal, lightVector), 0.5);
+
+ diffuse = diffuse * (1.0 / (1.0 + (0.00001 * distance * distance)));
+ v_Color = vec4(a_Color.rgb * diffuse, a_Color.a);
+ gl_Position = u_ModelViewProjection * a_Position;
+}
\ No newline at end of file
diff --git a/app/src/main/res/raw/line_frag.glsl b/app/src/main/res/raw/line_frag.glsl
new file mode 100755
index 0000000..17b7c6a
--- /dev/null
+++ b/app/src/main/res/raw/line_frag.glsl
@@ -0,0 +1,38 @@
+#extension GL_OES_standard_derivatives : enable
+precision mediump float;
+
+uniform float visibility;
+uniform float alphaTest;
+uniform float drawMode;
+uniform float nearCutOff;
+uniform float farCutOff;
+varying vec4 vColor;
+varying float depth;
+varying float vCounters;
+float map(float value, float inMin, float inMax, float outMin, float outMax) {
+// return outMin + (outMax - outMin) * (value - inMin) / (inMax - inMin);
+ return ((value - inMin) / (inMax - inMin) * (outMax - outMin) + outMin);
+}
+
+
+void main() {
+ vec4 c = vColor;
+ if( c.a < alphaTest ) discard;
+
+ if(drawMode > 0.0){
+ if(depth <= nearCutOff){
+ c.gb *= map(depth, nearCutOff, nearCutOff * 0.95, 0.0, 1.0);
+ }
+ if(depth > nearCutOff && depth < farCutOff){
+ c.gb *= 0.0;
+ }
+ if(depth >= farCutOff){
+ c.gb *= map(depth, farCutOff, farCutOff * 1.05, 0.0, 1.0);
+ }
+ }
+
+
+ gl_FragColor = c;
+
+ gl_FragColor.a *= step(vCounters,visibility);
+}
\ No newline at end of file
diff --git a/app/src/main/res/raw/line_vert.glsl b/app/src/main/res/raw/line_vert.glsl
new file mode 100755
index 0000000..966950a
--- /dev/null
+++ b/app/src/main/res/raw/line_vert.glsl
@@ -0,0 +1,93 @@
+precision mediump float;
+
+attribute vec3 position;
+attribute vec3 previous;
+attribute vec3 next;
+attribute float side;
+attribute float width;
+attribute float counters;
+
+uniform mat4 projectionMatrix;
+uniform mat4 modelViewMatrix;
+uniform vec2 resolution;
+uniform float lineWidth;
+uniform float lineDepthScale;
+uniform vec3 color;
+uniform float opacity;
+uniform float near;
+uniform float far;
+uniform float sizeAttenuation;
+
+varying vec4 vColor;
+varying float vCounters;
+varying float depth;
+
+vec2 fix( vec4 i, float aspect ) {
+ vec2 res = i.xy / i.w;
+ res.x *= aspect;
+ vCounters = counters;
+ return res;
+}
+
+float map(float value, float inMin, float inMax, float outMin, float outMax) {
+// return outMin + (outMax - outMin) * (value - inMin) / (inMax - inMin);
+ return ((value - inMin) / (inMax - inMin) * (outMax - outMin) + outMin);
+}
+
+void main() {
+
+ float aspect = resolution.x / resolution.y;
+ float pixelWidthRatio = 1. / (resolution.x * projectionMatrix[0][0]);
+
+ vColor = vec4( color, opacity );
+
+ mat4 m = projectionMatrix * modelViewMatrix;
+ vec4 finalPosition = m * vec4( position, 1.0 );
+ vec4 prevPos = m * vec4( previous, 1.0 );
+ vec4 nextPos = m * vec4( next, 1.0 );
+
+ vec2 currentP = fix( finalPosition, aspect );
+ vec2 prevP = fix( prevPos, aspect );
+ vec2 nextP = fix( nextPos, aspect );
+
+ float pixelWidth = finalPosition.w * pixelWidthRatio;
+ float w = 1.8 * pixelWidth * lineWidth * width;
+
+ if( sizeAttenuation == 1. ) {
+ w = 1.8 * lineWidth * width * map(clamp((finalPosition.z-near)/far, 0.0, 1.0), 0.0, 1.0, 1.0, lineDepthScale);
+ }
+
+ vec2 dir;
+ if( nextP == currentP ){
+ dir = normalize( currentP - prevP );
+ }
+ else if( prevP == currentP ){
+ dir = normalize( nextP - currentP );
+ }
+ else {
+ vec2 dir1 = normalize( currentP - prevP );
+ vec2 dir2 = normalize( nextP - currentP );
+ dir = normalize( dir1 + dir2 );
+
+ vec2 perp = vec2( -dir1.y, dir1.x );
+ vec2 miter = vec2( -dir.y, dir.x );
+// w = clamp( w / dot( miter, perp ), 0., 4. * lineWidth * width );
+
+ }
+
+ //vec2 normal = ( cross( vec3( dir, 0. ), vec3( 0., 0., 1. ) ) ).xy;
+ vec2 normal = vec2( -dir.y, dir.x );
+ normal.x /= aspect;
+ normal *= .5 * w;
+
+ vec4 offset = vec4( normal * side, 0.0, 1.0 );
+ finalPosition.xy += offset.xy;
+
+
+
+ gl_Position = finalPosition;
+
+ depth = gl_Position.z;
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/res/raw/model.sfb b/app/src/main/res/raw/model.sfb
new file mode 100644
index 0000000..3a532b2
Binary files /dev/null and b/app/src/main/res/raw/model.sfb differ
diff --git a/app/src/main/res/raw/object_fragment.shader b/app/src/main/res/raw/object_fragment.shader
new file mode 100755
index 0000000..730a105
--- /dev/null
+++ b/app/src/main/res/raw/object_fragment.shader
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+precision mediump float;
+
+uniform sampler2D u_Texture;
+
+uniform vec4 u_LightingParameters;
+uniform vec4 u_MaterialParameters;
+
+varying vec3 v_ViewPosition;
+varying vec3 v_ViewNormal;
+varying vec2 v_TexCoord;
+
+void main() {
+ // We support approximate sRGB gamma.
+ const float kGamma = 0.4545454;
+ const float kInverseGamma = 2.2;
+
+ // Unpack lighting and material parameters for better naming.
+ vec3 viewLightDirection = u_LightingParameters.xyz;
+ float lightIntensity = u_LightingParameters.w;
+
+ float materialAmbient = u_MaterialParameters.x;
+ float materialDiffuse = u_MaterialParameters.y;
+ float materialSpecular = u_MaterialParameters.z;
+ float materialSpecularPower = u_MaterialParameters.w;
+
+ // Normalize varying parameters, because they are linearly interpolated in the vertex shader.
+ vec3 viewFragmentDirection = normalize(v_ViewPosition);
+ vec3 viewNormal = normalize(v_ViewNormal);
+
+ // Apply inverse SRGB gamma to the texture before making lighting calculations.
+ // Flip the y-texture coordinate to address the texture from top-left.
+ vec4 objectColor = texture2D(u_Texture, vec2(v_TexCoord.x, 1.0 - v_TexCoord.y));
+ objectColor.rgb = pow(objectColor.rgb, vec3(kInverseGamma));
+
+ // Ambient light is unaffected by the light intensity.
+ float ambient = materialAmbient;
+
+ // Approximate a hemisphere light (not a harsh directional light).
+ float diffuse = lightIntensity * materialDiffuse *
+ 0.5 * (dot(viewNormal, viewLightDirection) + 1.0);
+
+ // Compute specular light.
+ vec3 reflectedLightDirection = reflect(viewLightDirection, viewNormal);
+ float specularStrength = max(0.0, dot(viewFragmentDirection, reflectedLightDirection));
+ float specular = lightIntensity * materialSpecular *
+ pow(specularStrength, materialSpecularPower);
+
+ // Apply SRGB gamma before writing the fragment color.
+ gl_FragColor.a = objectColor.a;
+ gl_FragColor.rgb = pow(objectColor.rgb * (ambient + diffuse) + specular, vec3(kGamma));
+}
diff --git a/app/src/main/res/raw/object_vertex.shader b/app/src/main/res/raw/object_vertex.shader
new file mode 100755
index 0000000..2e1e6a4
--- /dev/null
+++ b/app/src/main/res/raw/object_vertex.shader
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+uniform mat4 u_ModelView;
+uniform mat4 u_ModelViewProjection;
+
+attribute vec4 a_Position;
+attribute vec3 a_Normal;
+attribute vec2 a_TexCoord;
+
+varying vec3 v_ViewPosition;
+varying vec3 v_ViewNormal;
+varying vec2 v_TexCoord;
+
+void main() {
+ v_ViewPosition = (u_ModelView * a_Position).xyz;
+ v_ViewNormal = normalize((u_ModelView * vec4(a_Normal, 0.0)).xyz);
+ v_TexCoord = a_TexCoord;
+ gl_Position = u_ModelViewProjection * a_Position;
+}
diff --git a/app/src/main/res/raw/passthrough_fragment.shader b/app/src/main/res/raw/passthrough_fragment.shader
new file mode 100755
index 0000000..4e424dc
--- /dev/null
+++ b/app/src/main/res/raw/passthrough_fragment.shader
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+precision mediump float;
+varying vec4 v_Color;
+
+void main() {
+ gl_FragColor = v_Color;
+}
\ No newline at end of file
diff --git a/app/src/main/res/raw/plane_fragment.shader b/app/src/main/res/raw/plane_fragment.shader
new file mode 100755
index 0000000..d0a4708
--- /dev/null
+++ b/app/src/main/res/raw/plane_fragment.shader
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+precision highp float;
+uniform sampler2D u_Texture;
+uniform vec4 u_dotColor;
+uniform vec4 u_lineColor;
+uniform vec4 u_gridControl; // dotThreshold, lineThreshold, lineFadeShrink, occlusionShrink
+varying vec3 v_TexCoordAlpha;
+
+void main() {
+ vec4 control = texture2D(u_Texture, v_TexCoordAlpha.xy);
+ float dotScale = v_TexCoordAlpha.z;
+ float lineFade = max(0.0, u_gridControl.z * v_TexCoordAlpha.z - (u_gridControl.z - 1.0));
+ vec3 color = (control.r * dotScale > u_gridControl.x) ? u_dotColor.rgb
+ : (control.g > u_gridControl.y) ? u_lineColor.rgb * lineFade
+ : (u_lineColor.rgb * 0.25 * lineFade) ;
+ gl_FragColor = vec4(color, v_TexCoordAlpha.z * u_gridControl.w);
+}
diff --git a/app/src/main/res/raw/plane_vertex.shader b/app/src/main/res/raw/plane_vertex.shader
new file mode 100755
index 0000000..2b75a1f
--- /dev/null
+++ b/app/src/main/res/raw/plane_vertex.shader
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+uniform mat4 u_Model;
+uniform mat4 u_ModelViewProjection;
+uniform mat2 u_PlaneUvMatrix;
+
+attribute vec3 a_XZPositionAlpha; // (x, z, alpha)
+
+varying vec3 v_TexCoordAlpha;
+
+void main() {
+ vec4 position = vec4(a_XZPositionAlpha.x, 0.0, a_XZPositionAlpha.y, 1.0);
+ v_TexCoordAlpha = vec3(u_PlaneUvMatrix * (u_Model * position).xz, a_XZPositionAlpha.z);
+ gl_Position = u_ModelViewProjection * position;
+}
\ No newline at end of file
diff --git a/app/src/main/res/raw/point_cloud_vertex.shader b/app/src/main/res/raw/point_cloud_vertex.shader
new file mode 100755
index 0000000..627fc1a
--- /dev/null
+++ b/app/src/main/res/raw/point_cloud_vertex.shader
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+uniform mat4 u_ModelViewProjection;
+uniform vec4 u_Color;
+uniform float u_PointSize;
+
+attribute vec4 a_Position;
+
+varying vec4 v_Color;
+
+void main() {
+ v_Color = u_Color;
+ gl_Position = u_ModelViewProjection * vec4(a_Position.xyz, 1.0);
+ gl_PointSize = u_PointSize;
+}
diff --git a/app/src/main/res/raw/pyramid.sfb b/app/src/main/res/raw/pyramid.sfb
new file mode 100644
index 0000000..fc9dca1
Binary files /dev/null and b/app/src/main/res/raw/pyramid.sfb differ
diff --git a/app/src/main/res/raw/ring.sfb b/app/src/main/res/raw/ring.sfb
new file mode 100644
index 0000000..52a7043
Binary files /dev/null and b/app/src/main/res/raw/ring.sfb differ
diff --git a/app/src/main/res/raw/screenquad_fragment_oes.glsl b/app/src/main/res/raw/screenquad_fragment_oes.glsl
new file mode 100755
index 0000000..ae39692
--- /dev/null
+++ b/app/src/main/res/raw/screenquad_fragment_oes.glsl
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#extension GL_OES_EGL_image_external : require
+
+precision mediump float;
+varying vec2 v_TexCoord;
+uniform samplerExternalOES sTexture;
+
+
+void main() {
+ gl_FragColor = texture2D(sTexture, v_TexCoord);
+}
\ No newline at end of file
diff --git a/app/src/main/res/raw/screenquad_vertex.glsl b/app/src/main/res/raw/screenquad_vertex.glsl
new file mode 100755
index 0000000..0232dd7
--- /dev/null
+++ b/app/src/main/res/raw/screenquad_vertex.glsl
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+attribute vec4 a_Position;
+attribute vec2 a_TexCoord;
+
+varying vec2 v_TexCoord;
+
+void main() {
+ gl_Position = a_Position;
+ v_TexCoord = a_TexCoord;
+}
\ No newline at end of file
diff --git a/app/src/main/res/raw/stroller.sfb b/app/src/main/res/raw/stroller.sfb
new file mode 100644
index 0000000..0ab998c
Binary files /dev/null and b/app/src/main/res/raw/stroller.sfb differ
diff --git a/app/src/main/res/raw/wheelchair.sfb b/app/src/main/res/raw/wheelchair.sfb
new file mode 100644
index 0000000..bfa6538
Binary files /dev/null and b/app/src/main/res/raw/wheelchair.sfb differ
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index bdfe8d9..d6c5766 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -1,9 +1,7 @@
- #3f51b5
- #303F9F
- #FF4081
+ @android:color/holo_blue_dark
#66000000
@@ -18,6 +16,14 @@
#008c57
#0e1e49
+
+ @android:color/darker_gray
+
+ #666666
+ #599eff
+ #aaaaaa
+ #efefef
+
- #8BC34A
- #B71C1C
@@ -63,4 +69,30 @@
- @color/dot_inactive_screen3
+ #000000
+ #000000
+ #000000
+ #000000
+ #000000
+ #200000
+ #000000
+
+ #8000
+ #ffffff
+ #111111
+ #555555
+ #ffffff
+ #cccccc
+
+ #000000
+ #000000
+ #000000
+ #000000
+ #400000
+
+ #000000
+ #000000
+
+
+
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index e34e2c8..64597d4 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -1,8 +1,7 @@
- 16dp
- 16dp
+
16dp
30dp
20dp
@@ -12,4 +11,22 @@
30dp
16dp
40dp
+
+ 18sp
+ 14sp
+ 12sp
+ 16dp
+ 16dp
+ 264dp
+ 16dp
+ 14sp
+ 72dp
+ 16dp
+
+ 90dp
+ 1.5dp
+ 26sp
+ 14sp
+ 11sp
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 9cc567f..a7c267c 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,3 +1,4 @@
+
MapMint4ME
Table
@@ -94,4 +95,66 @@
To see the welcome slider again, goto Settings -> apps -> welcome slider -> clear data
Play Again
-
+
+
+ OK
+
+ Camera permission is needed to run this application
+
+ This device does not support AR.
+
+ Delete
+ Set as first
+ Horizontal move
+ Vertical move
+
+ Remove this cube.
+ Reorder and set this cube to the first.
+ Move on x and z axis.
+ Move on y axis.
+ Um_mph
+ UM_fps
+ UM_kn
+ UM_m
+ UM_ft
+ north
+ north_northeast
+ northeast
+ east_northeast
+ UM_km_h
+ UM_m_s
+ east
+ north_northwest
+ northwest
+ west_northwest
+ east_southeast
+ southeast
+ south_southeast
+ south
+ south_southwest
+ southwest
+ west_southwest
+ west
+ UM_km
+ UM_mi
+ UM_nm
+ toast_track_finished_click_again
+ dlg_app_killed
+ open_android_app_settings
+ about_ok
+ dlg_app_killed_description
+ toast_active_track_not_empty
+ please_grant_storage_permission
+ toast_track_saved_into_tracklist
+ tab_gpsfix
+ toast_track_exported
+ export_unable_to_write_file
+ message_exit_finalizing
+ yes
+ no
+ gps_disabled_with_hint
+ gps_out_of_service
+ gps_searching
+ gps_stabilizing
+ GPS coordinates
+
\ No newline at end of file
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 7f8367e..d15111c 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -6,6 +6,56 @@
- @color/colorPrimary
- @color/colorPrimaryDark
- @color/colorAccent
+ - false
+ - true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/xml/my_backup_rules.xml b/app/src/main/res/xml/my_backup_rules.xml
new file mode 100644
index 0000000..82a84d3
--- /dev/null
+++ b/app/src/main/res/xml/my_backup_rules.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml
new file mode 100644
index 0000000..042c4b3
--- /dev/null
+++ b/app/src/main/res/xml/network_security_config.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build.gradle b/build.gradle
index 8c7db9d..f3bce10 100644
--- a/build.gradle
+++ b/build.gradle
@@ -10,7 +10,9 @@ buildscript {
google()
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.4.1'
+ classpath 'com.android.tools.build:gradle:4.0.1'
+ classpath 'com.google.ar.sceneform:plugin:1.8.0'
+ classpath 'com.google.gms:google-services:4.2.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
@@ -20,6 +22,7 @@ buildscript {
allprojects {
repositories {
jcenter()
+ //maven { url 'https://jitpack.io' }
google()
}
}
diff --git a/examples/Final (1).jpg b/examples/Final (1).jpg
new file mode 100644
index 0000000..4e3c37a
Binary files /dev/null and b/examples/Final (1).jpg differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index d166076..8c43f11 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Thu Apr 18 16:04:19 CEST 2019
+#Thu Jun 11 12:17:09 IST 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
diff --git a/output/Screenshot_2020-08-30-00-34-10-04.jpg b/output/Screenshot_2020-08-30-00-34-10-04.jpg
new file mode 100644
index 0000000..cf814ca
Binary files /dev/null and b/output/Screenshot_2020-08-30-00-34-10-04.jpg differ
diff --git a/output/Screenshot_2020-08-30-00-34-23-16.jpg b/output/Screenshot_2020-08-30-00-34-23-16.jpg
new file mode 100644
index 0000000..4cc254d
Binary files /dev/null and b/output/Screenshot_2020-08-30-00-34-23-16.jpg differ
diff --git a/output/Screenshot_2020-08-30-00-34-33-76.jpg b/output/Screenshot_2020-08-30-00-34-33-76.jpg
new file mode 100644
index 0000000..6194ba7
Binary files /dev/null and b/output/Screenshot_2020-08-30-00-34-33-76.jpg differ
diff --git a/output/Screenshot_2020-08-30-00-34-42-92.jpg b/output/Screenshot_2020-08-30-00-34-42-92.jpg
new file mode 100644
index 0000000..c5e99f0
Binary files /dev/null and b/output/Screenshot_2020-08-30-00-34-42-92.jpg differ
diff --git a/output/op1.jpg b/output/op1.jpg
new file mode 100644
index 0000000..faaa443
Binary files /dev/null and b/output/op1.jpg differ
diff --git a/output/op2.jpg b/output/op2.jpg
new file mode 100644
index 0000000..bb00e9a
Binary files /dev/null and b/output/op2.jpg differ
diff --git a/output/output.png b/output/output.png
new file mode 100644
index 0000000..76b6aea
Binary files /dev/null and b/output/output.png differ
diff --git a/output/output_video.mp4 b/output/output_video.mp4
new file mode 100644
index 0000000..e836f4a
Binary files /dev/null and b/output/output_video.mp4 differ