如何在android中制作gpx文件

Posted

技术标签:

【中文标题】如何在android中制作gpx文件【英文标题】:how to make gpx file in android 【发布时间】:2020-08-15 19:54:49 【问题描述】:

我想用 DOM 和 Transformer 编写 gpx 文件

我的代码是这样的

try 
    val document =
      DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument()
    val trkpt = document.createElement("trkpt")
    trkpt.setAttribute("lat", "-33.626932")
    trkpt.setAttribute("lon", "-33.626932")
    val ele = document.createElement("ele")
    ele.appendChild(document.createTextNode("-6"))
    trkpt.appendChild(ele)
    document.appendChild(trkpt)
    val transformer = TransformerFactory.newInstance().newTransformer()
    transformer.setOutputProperty(OutputKeys.METHOD,"gpx")

    val saveFolder = File(folderPath) // 저장 경로
    if (!saveFolder.exists())        //폴더 없으면 생성
      saveFolder.mkdir()
    
    val path = "route_$System.currentTimeMillis().gpx"
    val file = File(saveFolder, path)         //로컬에 파일저장

    val source = DOMSource(document)
    //val result = StreamResult(FileOutputStream(file))
    val result = StreamResult(System.out)
    transformer.transform(source, result)
    return Uri.fromFile(file)
  catch(e:Exception)
    e.printStackTrace()
  

输出是这样的

<?xml version="1.0" encoding="UTF-8"?><trkpt lat="-33.626932" lon="-33.626932"><ele>-6</ele></trkpt>

但我想把标签改成这样

<gpx xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" creator="TraceDeTrail http://www.tracedetrail.fr" version="1.1" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd ">

我怎样才能这样编辑,属性也是如此

我尝试从 JPX 更改为我自己的原因是我需要从航点获取时间。 enter image description here

有时,我需要从 WayPoint 类中获取时间,但时间的类型是 ZonedDateTime。但它不适用于 SDK 24...是否有任何解决方案从航点获取时间?


我添加了 ThreeTenABP,但我不知道我是如何使用它的。 我在 Gradle 上添加库并在 app-instance 中初始化 但它仍然会出错

 wpList.add(
      WayPoint.builder()
        .lat(currentLatLng.latitude)
        .lon(currentLatLng.longitude)
        .name("Start")
        .desc("Start Description")
        .time(System.currentTimeMillis())  <- RunningActivity.kt:107
        .type(START_POINT)
        .build()
    )
2020-05-06 16:46:16.895 8877-8877/com.umpa2020.tracer E/androidRuntime: FATAL EXCEPTION: main
    Process: com.umpa2020.tracer, PID: 8877
    java.lang.NoClassDefFoundError: Failed resolution of: Ljava/time/Instant;
        at io.jenetics.jpx.WayPoint$Builder.time(WayPoint.java:767)
        at com.umpa2020.tracer.main.start.running.RunningActivity.start(RunningActivity.kt:107)
        at com.umpa2020.tracer.main.start.running.RunningActivity.onSingleClick(RunningActivity.kt:175)
        at com.umpa2020.tracer.util.OnSingleClickListener$DefaultImpls.onClick(OnSingleClickListener.kt:19)
        at com.umpa2020.tracer.main.start.BaseRunningActivity.onClick(BaseRunningActivity.kt:45)
        at android.view.View.performClick(View.java:5610)
        at android.view.View$PerformClick.run(View.java:22265)
        at android.os.Handler.handleCallback(Handler.java:751)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:154)
        at android.app.ActivityThread.main(ActivityThread.java:6077)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)
     Caused by: java.lang.ClassNotFoundException: Didn't find class "java.time.Instant" on path: DexPathList[[zip file "/data/app/com.umpa2020.tracer-2/base.apk"],nativeLibraryDirectories=[/data/app/com.umpa2020.tracer-2/lib/x86, /system/lib, /vendor/lib]]
        at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:380)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
        at io.jenetics.jpx.WayPoint$Builder.time(WayPoint.java:767) 
        at com.umpa2020.tracer.main.start.running.RunningActivity.start(RunningActivity.kt:107) 
        at com.umpa2020.tracer.main.start.running.RunningActivity.onSingleClick(RunningActivity.kt:175) 
        at com.umpa2020.tracer.util.OnSingleClickListener$DefaultImpls.onClick(OnSingleClickListener.kt:19) 
        at com.umpa2020.tracer.main.start.BaseRunningActivity.onClick(BaseRunningActivity.kt:45) 
        at android.view.View.performClick(View.java:5610) 
        at android.view.View$PerformClick.run(View.java:22265) 
        at android.os.Handler.handleCallback(Handler.java:751) 
        at android.os.Handler.dispatchMessage(Handler.java:95) 
        at android.os.Looper.loop(Looper.java:154) 
        at android.app.ActivityThread.main(ActivityThread.java:6077) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:756) 

【问题讨论】:

【参考方案1】:

gpx 本质上是一个特殊的 xml。您所说的是 XML 命名空间

创建方法:ho to create XML header

一个xml文件只能有一个根元素,但一个gpx文件中有这么多trkpt元素。所以trkpt item 不应该是根元素。您应该将所有 trkpt 元素包含在单个 gpx 元素中,这是 gpx 文件的根元素。

要在 Android 中生成 gpx 文件,更好的选择是使用库。他们中的大多数可以帮助您进行上述所有操作。

我在我的 android 项目中使用io.jenetics.jpx,它工作正常。

XML 不支持附加元素。如果您想在记录时生成 gpx 文件,您应该以可附加的形式执行此操作,例如任何 Serializable 类型。完成记录后,您可以从序列化文件创建一个有效的 GPX 文件。

jpx中的WayPoint是Serializable类型,我正在使用。

你也可以使用AndroidLocation,它实现了Parcelable,类似Serializable的android实现。

我的代码:

public class TrackService extends Service 
    private int notificationId = 142857 ;
    NotificationCompat.Builder builder;
    NotificationManagerCompat notificationManager;
    private Timer timer = new Timer(true);
    private static final String CHANNEL_ID = "org.kib.qtp";
    private File serializerFile;
    private ObjectOutputStream oos;
    private Long basetime;
    SimpleDateFormat sdf;

    @Override
    public void onCreate()
        super.onCreate();
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), 0);
        builder = new NotificationCompat.Builder(this, CHANNEL_ID)
                .setSmallIcon(R.drawable.ic_map_black_24dp)
                .setContentTitle(getString(R.string.track))
                .setContentText(getString(R.string.tracktext))
                .setPriority(NotificationCompat.PRIORITY_LOW)
                .setOngoing(true)
                .setOnlyAlertOnce(true)
                .setContentIntent(pendingIntent);
        notificationManager = NotificationManagerCompat.from(this);
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
            startForeground (notificationId, builder.build());
        else
            notificationManager.notify(notificationId, builder.build());
    

    @Override
    public void onDestroy()
        try 
            endTrack();
         catch (IOException e) 
            e.printStackTrace();
        
        stopForeground(true);
        super.onDestroy();
    

    @Nullable
    @Override
    public IBinder onBind(Intent intent) 
        return null;
    

    @Override
    public int onStartCommand(Intent intent, int flags, int startId)
        /**
         * @param ele require altitude or not
         * @param time update time
         * @param fine fine location or not
         * @param name the gpx file's name
         */
        sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", getResources(). getConfiguration().locale);

        int time = 60000;
        boolean ele = false;
        boolean fine = false;
        String name = Calendar.getInstance().getTime().toString().replaceAll(" ", "_");

        if (intent.hasExtra("time"))
            Objects.requireNonNull(intent.getExtras()).getInt("time");
        if (intent.hasExtra("ele"))
            Objects.requireNonNull(intent.getExtras()).getBoolean("ele");
        if (intent.hasExtra("fine"))
            Objects.requireNonNull(intent.getExtras()).getBoolean("fine");
        if (intent.hasExtra("name"))
            Objects.requireNonNull(intent.getExtras()).getString("name");

        File path = new File(getFilesDir().getAbsolutePath() + "/gpx");
        while(!path.exists())
            path.mkdirs();
        try 
            serializerFile = new File(getFilesDir().getAbsolutePath() + "/gpx/" + name );
            if (serializerFile.exists())
                serializerFile.delete();
            while(!serializerFile.exists())
                serializerFile.createNewFile();
            oos = new ObjectOutputStream(new FileOutputStream(serializerFile));
         catch (IOException e) 
            e.printStackTrace();
        
        basetime = 0L;
        Toast.makeText(this, R.string.toast_start_tracking, Toast.LENGTH_SHORT).show();
        startTrack(time, ele, fine);
        return START_STICKY;
    

    private void endTrack() throws IOException 
        timer.cancel();
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(serializerFile));
        TrackSegment.Builder segment = TrackSegment.builder();
        while (true) 
            try
                WayPoint wp = (WayPoint) ois.readObject();
                segment.addPoint(wp);
             catch (EOFException e) 
                break;
             catch (ClassNotFoundException ignored)

            
        
        final GPX gpx = GPX.builder().addTrack(track -> track.addSegment(segment.build())).build();
        GPX.write(gpx, serializerFile.getAbsolutePath() + ".gpx");
        Toast.makeText(this, R.string.toast_gpx_write, Toast.LENGTH_SHORT).show();
        new File(serializerFile.getAbsolutePath() + ".tobeupload").createNewFile();;
    

    private void startTrack(int time, boolean ele, boolean fine) 

        TimerTask task = new TimerTask() 
            public void run() 
                Location location = getLocation(ele, fine);
                if (location != null)
                    try 
                        if (location.getTime() != basetime)
                            basetime = location.getTime();
                            WayPoint point = WayPoint.builder().lat(location.getLatitude()).lon(location.getLongitude()).ele(location.getAltitude()).time(location.getTime()).build();
                            if (point != null)
                                oos.writeObject(point);
                                NotificationCompat.Builder timerBuilder = builder.setContentText(getString(R.string.tracktext) + sdf.format(location.getTime()));
                                Log.i("track", location.getLatitude() +","+ location.getLongitude() +","+ location.getAltitude());
                                notificationManager.notify(notificationId,timerBuilder.build());
                            
                        

                     catch (IOException e) 
                        e.printStackTrace();
                    
                
            
        ;  
        timer.schedule(task, 0, time);  
    

    private Location getLocation(boolean ele, boolean fine) 
        //get location here
    

jpx 包括 zonedDateTime 类,它在 java8 中工作。但它的创建者 Stephen Colebourne 将它向后移植到 Java 6,并且作为库也向后移植到了早期的 android 版本。

图书馆在这里:JakeWharton/ThreeTenABP - An adaptation of the JSR-310 backport for Android.

以及如何使用:How to use ThreeTenABP in Android Project - ***

【讨论】:

其实我用的是 JPX 库。但我的应用程序的最低 sdk 版本是 24。这意味着我需要使用 java 7。但是 jpx 库包含 zoneddatetime 类......它适用于 java8。所以我想我需要创建自己......你知道其他图书馆吗?? jpx 在我的项目中运行良好,它的 min sdk 为 21。您可以使用 Long 变量作为时间。例如 location.getTime()。 @Yunkwon umm.. 我从 jpx 更改为我自己的原因是,我需要从航路点获取时间...我编辑帖子的结尾。请查看并回复..我需要你的帮助... 好的,我明白了。 java 8 中的 java.time.* 包已由其创建者 Stephen Colebourne 向后移植到 Java 6,并且也向后移植到早期的 android 版本。在这里:github.com/JakeWharton/ThreeTenABP 我又有一个问题...我将 ThreeTenABP 添加到我的项目中。并在我的 App 实例中初始化。但我认为这是行不通的。那么如何使用呢?我在帖子末尾添加了更多相关信息

以上是关于如何在android中制作gpx文件的主要内容,如果未能解决你的问题,请参考以下文章

在适当的外部地图应用程序中打开 KML/GPX 文件

【小程序】地图|绘制GPX轨迹

如何从 GPX 文件中提取高程数据

GPX 文件不会在 Android 模拟器中加载

如何在 gpx 文件中找到第 10 个跟踪点

如何在谷歌地图中绘制gpx文件