Порт сервера на публичный сервер
Этот коммит содержится в:
Коммит
5c026601e1
21
LICENSE
Обычный файл
21
LICENSE
Обычный файл
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 SWD Embedded Systems Ltd
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
30
README.md
Обычный файл
30
README.md
Обычный файл
@ -0,0 +1,30 @@
|
||||
# gis-map-server
|
||||
Веб-сервис для распространения и тестовый клиент для отображения картографической информации.
|
||||
|
||||
[gis-map-server](src/gis-map-server/) - Python веб-сервер
|
||||
|
||||
[gis-client-render](src/gis-client-render) - Qt клиент для отображения
|
||||
|
||||
Архитектура проекта:
|
||||
![GIS Map Server](src/gis-map-server/docs/architecture.drawio.png)
|
||||
|
||||
Сервис принимает запросы на отрисовку региона средствами [ПК ЦКИ](https://kpda.ru/products/gis/) и возвращает отрисованную карту в виде картинки (поддерживаемые выходные форматы: bmp, png, jpeg).
|
||||
|
||||
Для сборки необходимо:
|
||||
```
|
||||
cd src
|
||||
# Единожды сформировать файл переводов
|
||||
lrelease gis-client-render/*pro
|
||||
# Для сборки под архитектуру _cpu_ можно установить CPULIST=_cpu_, например x86
|
||||
make install
|
||||
```
|
||||
В результате в корне проекта появится папка `.install`, в которую инсталлируются runtime-компоненты
|
||||
|
||||
При запуске веб-сервер `gis-map-server` считывает [конфигурационный файл](src/gis-map-server/gis-map-server.conf).
|
||||
`gis-map-server` предоставляет веб-интерфейс, внешний вид которого показан на рисунке 1.
|
||||
|
||||
![Рисунок 1](src/gis-map-server/docs/web_interface1.png)
|
||||
|
||||
Внешний вид Qt-клиента показан на рисунке 2.
|
||||
|
||||
![Рисунок 2](src/gis-client-render/images/client_interface1.png)
|
2
src/Makefile
Обычный файл
2
src/Makefile
Обычный файл
@ -0,0 +1,2 @@
|
||||
LIST=LIB
|
||||
include recurse.mk
|
2
src/gis-client-render/Makefile
Обычный файл
2
src/gis-client-render/Makefile
Обычный файл
@ -0,0 +1,2 @@
|
||||
LIST=OS
|
||||
include recurse.mk
|
21
src/gis-client-render/common.mk
Обычный файл
21
src/gis-client-render/common.mk
Обычный файл
@ -0,0 +1,21 @@
|
||||
ifndef QCONFIG
|
||||
QCONFIG=qconfig.mk
|
||||
endif
|
||||
include $(QCONFIG)
|
||||
|
||||
COMPILER_DRIVER:=
|
||||
include $(MKFILES_ROOT)/buildlist.mk
|
||||
ifndef OS
|
||||
include $(MKFILES_ROOT)/qmacros.mk
|
||||
endif
|
||||
|
||||
USEFILE=
|
||||
|
||||
# GIS package options
|
||||
ifneq ( $(GIS_INSTALL_ROOT), )
|
||||
DESTDIR:=$(GIS_INSTALL_ROOT)
|
||||
endif
|
||||
export GIS_INSTALL_ROOT=$(DESTDIR)
|
||||
|
||||
include $(MKFILES_ROOT)/qmake.mk
|
||||
include $(MKFILES_ROOT)/qtargets.mk
|
51
src/gis-client-render/gis-client-render.pro
Обычный файл
51
src/gis-client-render/gis-client-render.pro
Обычный файл
@ -0,0 +1,51 @@
|
||||
QT += core gui network
|
||||
|
||||
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
||||
|
||||
qnx {
|
||||
# Get install directory for defining command to run
|
||||
INSTALLDIRECTORY=$$(INSTALLDIR)
|
||||
|
||||
!isEmpty(INSTALLDIRECTORY) {
|
||||
target.path = $$(INSTALLDIR)
|
||||
}
|
||||
|
||||
isEmpty(INSTALLDIRECTORY) {
|
||||
INSTALLDIRECTORY = "/opt/gis/bin"
|
||||
target.path = $$INSTALLDIRECTORY
|
||||
message(Manual path setting $$INSTALLDIRECTORY)
|
||||
}
|
||||
}
|
||||
|
||||
TEMPLATE = app
|
||||
|
||||
INSTALLS += target
|
||||
|
||||
# The following define makes your compiler emit warnings if you use
|
||||
# any feature of Qt which as been marked as deprecated (the exact warnings
|
||||
# depend on your compiler). Please consult the documentation of the
|
||||
# deprecated API in order to know how to port your code away from it.
|
||||
DEFINES += QT_DEPRECATED_WARNINGS
|
||||
|
||||
SOURCES += main.cpp\
|
||||
mainwidget.cpp \
|
||||
mainwindow.cpp \
|
||||
image_viewport.cpp \
|
||||
gismapserver.cpp \
|
||||
mapsender.cpp
|
||||
|
||||
HEADERS += mainwidget.h \
|
||||
mainwindow.h \
|
||||
image_viewport.h \
|
||||
gismapserver.h \
|
||||
mapsender.h
|
||||
|
||||
# If: Cannot find file 'translations/gis-client-render_ru.qm'
|
||||
# then run lupdate, lrelease manually
|
||||
TRANSLATIONS += $$_PRO_FILE_PWD_/translations/$$join(TARGET,,,_ru.ts)
|
||||
|
||||
RESOURCES += \
|
||||
images.qrc \
|
||||
translations.qrc
|
||||
|
||||
QMAKE_CXXFLAGS += -std=gnu++11
|
52
src/gis-client-render/gismapserver.cpp
Обычный файл
52
src/gis-client-render/gismapserver.cpp
Обычный файл
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* (c) 2011-2022, SWD Embedded Systems Limited, http://www.kpda.ru
|
||||
*/
|
||||
|
||||
#include "gismapserver.h"
|
||||
#include "mapsender.h"
|
||||
|
||||
QString gis_map_server_get_error_message( GisHttpCodes statusCode )
|
||||
{
|
||||
switch ( statusCode ) {
|
||||
case GisHttpCodes::IsProcessing :
|
||||
return "Is processing";
|
||||
case GisHttpCodes::InvalidParameters :
|
||||
return "Invalid parameters";
|
||||
case GisHttpCodes::RequestFailed :
|
||||
return "Failed to request";
|
||||
case GisHttpCodes::Timeout :
|
||||
return "Timeout while processing request";
|
||||
case GisHttpCodes::RenderFailed :
|
||||
return "Failed to render request";
|
||||
case GisHttpCodes::Done :
|
||||
return "Request has been already obtained";
|
||||
case GisHttpCodes::Ok :
|
||||
default:
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
|
||||
int gis_map_server_request_map( const char *server_url,
|
||||
double lat,
|
||||
double lon,
|
||||
int scale,
|
||||
int w,
|
||||
int h,
|
||||
const char *format,
|
||||
uint *orderId,
|
||||
char *pinCode )
|
||||
{
|
||||
if ( scale <= 0 || w <= 0 || h <= 0 || !orderId || !format )
|
||||
return EINVAL;
|
||||
|
||||
uint timeout_ms = 30000;
|
||||
MapSender mapSender( QString(server_url), 0, timeout_ms );
|
||||
return mapSender.sendRequest( lat, lon, scale, w, h, format, orderId, pinCode );
|
||||
}
|
||||
|
||||
int gis_map_server_get_image( const char *server_url, int orderId, char *pinCode, QImage *image )
|
||||
{
|
||||
uint timeout_ms = 30000;
|
||||
MapSender mapSender( QString(server_url), orderId, timeout_ms );
|
||||
return mapSender.getImageWithWaiting( pinCode, image );
|
||||
}
|
44
src/gis-client-render/gismapserver.h
Обычный файл
44
src/gis-client-render/gismapserver.h
Обычный файл
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* (c) 2011-2022, SWD Embedded Systems Limited, http://www.kpda.ru
|
||||
*/
|
||||
|
||||
/*************************************************/
|
||||
/* GIS Map Server */
|
||||
/* Server interface header */
|
||||
/*************************************************/
|
||||
|
||||
|
||||
#ifndef GISMAPSENDER_H
|
||||
#define GISMAPSENDER_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
#include <QImage>
|
||||
#include <QString>
|
||||
|
||||
|
||||
typedef enum {
|
||||
Ok = 200,
|
||||
IsProcessing = 202,
|
||||
InvalidParameters = 400,
|
||||
Timeout = 408,
|
||||
Done = 410,
|
||||
NoMemory = 418,
|
||||
RenderFailed = 500,
|
||||
RequestFailed = 520
|
||||
} GisHttpCodes;
|
||||
|
||||
QString gis_map_server_get_error_message( GisHttpCodes statusCode );
|
||||
|
||||
/*
|
||||
* Make a request to the gis-map-server
|
||||
*
|
||||
* format -png, jpeg, bmp
|
||||
*/
|
||||
int gis_map_server_request_map(const char *server_url, double lat, double lon, int scale, int w, int h, const char *format, uint* orderId, char *pinCode );
|
||||
|
||||
int gis_map_server_get_image(const char *server_url, int orderId, char *pinCode, QImage *image );
|
||||
|
||||
#endif
|
||||
|
||||
#endif // GISMAPSENDER_H
|
92
src/gis-client-render/image_viewport.cpp
Обычный файл
92
src/gis-client-render/image_viewport.cpp
Обычный файл
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* (c) 2011-2022, SWD Embedded Systems Limited, http://www.kpda.ru
|
||||
*/
|
||||
|
||||
#include "image_viewport.h"
|
||||
|
||||
#include <QGraphicsPixmapItem>
|
||||
#include <QLabel>
|
||||
#include <QMovie>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
ImageViewport::ImageViewport() : QGraphicsView(),
|
||||
currentMapItem( nullptr )
|
||||
{
|
||||
/*
|
||||
* Animated earth's GIF setup
|
||||
* Runs until user performs a request
|
||||
*
|
||||
* See: https://forum.qt.io/topic/15658/solved-how-to-play-gif-animation-in-qgraphicsview-widget
|
||||
*/
|
||||
graphics_scene = new QGraphicsScene();
|
||||
graphics_scene->setBackgroundBrush( QColor( 239, 239, 239 ) );
|
||||
QLabel *animation = new QLabel();
|
||||
QMovie *earth_gif = new QMovie( ":/images/earth.gif" );
|
||||
animation->setMovie( earth_gif );
|
||||
earth_gif->setSpeed( 90 );
|
||||
earth_gif->start();
|
||||
proxy = graphics_scene->addWidget( animation );
|
||||
|
||||
/*
|
||||
* Placing a circle around GIF to hide its low resolution
|
||||
*/
|
||||
QPen earth_bound( (QColor( Qt::black )) );
|
||||
earth_bound.setWidth( 5 );
|
||||
graphics_scene->addEllipse( QRectF( 0, 0, 257, 257 ),
|
||||
QPen( earth_bound ),
|
||||
QBrush( QColor( 0, 0, 0, 0 ) ) );
|
||||
|
||||
setScene( graphics_scene );
|
||||
setDragMode( QGraphicsView::ScrollHandDrag );
|
||||
setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
|
||||
setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
|
||||
}
|
||||
|
||||
void ImageViewport::fit_image()
|
||||
{
|
||||
/*
|
||||
* Place the center of the image at the center of the viewport
|
||||
*/
|
||||
centerOn( graphics_scene->width() / 2,
|
||||
graphics_scene->height() / 2 );
|
||||
|
||||
/*
|
||||
* Keep the whole image inside the viewport, saving the aspect ratio
|
||||
*/
|
||||
fitInView( QRectF( 0, 0,
|
||||
graphics_scene->width(),
|
||||
graphics_scene->height() ),
|
||||
Qt::KeepAspectRatio );
|
||||
}
|
||||
|
||||
void ImageViewport::wheelEvent( QWheelEvent *event )
|
||||
{
|
||||
if ( event->delta() > 0 )
|
||||
scale( scroll_step, scroll_step );
|
||||
|
||||
if ( event->delta() < 0 )
|
||||
scale( 1 / scroll_step, 1 / scroll_step );
|
||||
}
|
||||
|
||||
void ImageViewport::show_map_image( const QImage &image )
|
||||
{
|
||||
proxy->hide();
|
||||
if ( currentMapItem )
|
||||
graphics_scene->removeItem( currentMapItem );
|
||||
|
||||
currentMapItem = new QGraphicsPixmapItem( QPixmap::fromImage( image ) );
|
||||
graphics_scene->addItem( currentMapItem );
|
||||
fit_image();
|
||||
}
|
||||
|
||||
void ImageViewport::clean_map_image()
|
||||
{
|
||||
proxy->show();
|
||||
if ( currentMapItem ) {
|
||||
graphics_scene->removeItem( currentMapItem );
|
||||
currentMapItem = nullptr;
|
||||
}
|
||||
|
||||
fit_image();
|
||||
}
|
34
src/gis-client-render/image_viewport.h
Обычный файл
34
src/gis-client-render/image_viewport.h
Обычный файл
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* (c) 2011-2022, SWD Embedded Systems Limited, http://www.kpda.ru
|
||||
*/
|
||||
|
||||
#ifndef IMAGE_VIEWPORT_H
|
||||
#define IMAGE_VIEWPORT_H
|
||||
|
||||
#include <QEvent>
|
||||
#include <QGraphicsScene>
|
||||
#include <QGraphicsView>
|
||||
#include <QWheelEvent>
|
||||
#include <QGraphicsProxyWidget>
|
||||
|
||||
class ImageViewport : public QGraphicsView
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ImageViewport();
|
||||
|
||||
void fit_image();
|
||||
protected:
|
||||
virtual void wheelEvent( QWheelEvent *event );
|
||||
private:
|
||||
const double scroll_step = 1.3;
|
||||
QGraphicsScene *graphics_scene;
|
||||
QGraphicsProxyWidget *proxy;
|
||||
QGraphicsPixmapItem *currentMapItem;
|
||||
public slots:
|
||||
void show_map_image( const QImage &img );
|
||||
void clean_map_image();
|
||||
};
|
||||
|
||||
#endif // IMAGE_VIEWPORT_H
|
5
src/gis-client-render/images.qrc
Обычный файл
5
src/gis-client-render/images.qrc
Обычный файл
@ -0,0 +1,5 @@
|
||||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>images/earth.gif</file>
|
||||
</qresource>
|
||||
</RCC>
|
Двоичные данные
src/gis-client-render/images/client_interface1.png
Обычный файл
Двоичные данные
src/gis-client-render/images/client_interface1.png
Обычный файл
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 435 KiB |
Двоичные данные
src/gis-client-render/images/earth.gif
Обычный файл
Двоичные данные
src/gis-client-render/images/earth.gif
Обычный файл
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 739 KiB |
64
src/gis-client-render/main.cpp
Обычный файл
64
src/gis-client-render/main.cpp
Обычный файл
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* (c) 2011-2022, SWD Embedded Systems Limited, http://www.kpda.ru
|
||||
*/
|
||||
|
||||
#include <cstring>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
#include <QTranslator>
|
||||
|
||||
#include "mainwidget.h"
|
||||
#include "mainwindow.h"
|
||||
|
||||
int main( int argc, char *argv[] )
|
||||
{
|
||||
double x_pos = -999, y_pos = -999;
|
||||
uint32_t width = 0, height = 0, scale = 0;
|
||||
const char *server_url = nullptr;
|
||||
const char *image_format = nullptr;
|
||||
const char *outputImageName = nullptr;
|
||||
bool consoleMode = false;
|
||||
|
||||
QLocale::setDefault(QLocale::C);
|
||||
|
||||
int opt = 0;
|
||||
extern char *optarg;
|
||||
while ( ( opt = getopt( argc, argv, "x:y:w:h:s:u:f:o:" ) ) != -1 )
|
||||
{
|
||||
switch ( opt )
|
||||
{
|
||||
case 'x': x_pos = atof( optarg ); break;
|
||||
case 'y': y_pos = atof( optarg ); break;
|
||||
case 'w': width = strtoul( optarg, NULL, 0 ); break;
|
||||
case 'h': height = strtoul( optarg, NULL, 0 ); break;
|
||||
case 's': scale = strtoul( optarg, NULL, 0 ); break;
|
||||
case 'u': server_url = strdup( optarg ); break;
|
||||
case 'f': image_format = strdup( optarg ); break;
|
||||
case 'o':
|
||||
{
|
||||
consoleMode = true;
|
||||
outputImageName = strdup( optarg );
|
||||
break;
|
||||
}
|
||||
|
||||
case '?': printf( "Error: unknown option\n" ); exit(1); break;
|
||||
};
|
||||
};
|
||||
|
||||
QApplication app( argc, argv );
|
||||
|
||||
QString ablangStr = getenv( "ABLANG" );
|
||||
if ( ablangStr.isEmpty() ) {
|
||||
ablangStr = getenv( "LANG" );
|
||||
}
|
||||
QTranslator myTranslator;
|
||||
myTranslator.load( ":/translations/gis-client-render_" + ablangStr );
|
||||
app.installTranslator( &myTranslator );
|
||||
|
||||
MainWindow window( x_pos, y_pos, width, height, scale, server_url, image_format, outputImageName, consoleMode );
|
||||
window.show();
|
||||
|
||||
return app.exec();
|
||||
}
|
264
src/gis-client-render/mainwidget.cpp
Обычный файл
264
src/gis-client-render/mainwidget.cpp
Обычный файл
@ -0,0 +1,264 @@
|
||||
/*
|
||||
* (c) 2011-2022, SWD Embedded Systems Limited, http://www.kpda.ru
|
||||
*/
|
||||
|
||||
#include "mainwidget.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
#include <QLabel>
|
||||
#include <QHBoxLayout>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include <QMutex>
|
||||
#include <QWaitCondition>
|
||||
#include <QDir>
|
||||
|
||||
#include "gismapserver.h"
|
||||
|
||||
MainWidget::MainWidget( double x_pos,
|
||||
double y_pos,
|
||||
uint32_t width,
|
||||
uint32_t height,
|
||||
uint32_t scale,
|
||||
const char *server_url,
|
||||
const char *image_format,
|
||||
const char *outputImageName,
|
||||
bool consoleMode ) : QWidget(),
|
||||
_consoleMode( consoleMode ),
|
||||
_outputImageName( outputImageName ),
|
||||
_server_url( server_url ),
|
||||
_image_format( image_format ),
|
||||
request_btn_state( true ),
|
||||
field_x_pos( new QLineEdit() ),
|
||||
field_y_pos( new QLineEdit() ),
|
||||
field_width( new QLineEdit() ),
|
||||
field_height( new QLineEdit() ),
|
||||
field_scale( new QLineEdit() ),
|
||||
urlEdit(new QLineEdit()),
|
||||
formatBox(new QComboBox()),
|
||||
request_btn( new QPushButton( tr( "Request map" ) ) )
|
||||
{
|
||||
/*
|
||||
* Do not rescale left dock panel's widgets and contents.
|
||||
*/
|
||||
QSizePolicy size_policy;
|
||||
size_policy.setVerticalPolicy( QSizePolicy::Fixed );
|
||||
size_policy.setHorizontalPolicy( QSizePolicy::Fixed );
|
||||
setSizePolicy( size_policy );
|
||||
|
||||
/*
|
||||
* Placing console parameters into fields, if they are present
|
||||
* If they are not present, use placeholders with input hints.
|
||||
*/
|
||||
if ( width != 0 )
|
||||
field_width->setText( QString::number( width, 10 ) );
|
||||
|
||||
if ( height != 0 )
|
||||
field_height->setText( QString::number( height, 10 ) );
|
||||
|
||||
if ( x_pos != -999 )
|
||||
field_x_pos->setText( QString::number( x_pos, 'f', 6 ) );
|
||||
else
|
||||
field_x_pos->setPlaceholderText( QString( "[-180...180]" ) );
|
||||
|
||||
if ( y_pos != -999 )
|
||||
field_y_pos->setText( QString::number( y_pos, 'f', 6 ) );
|
||||
else
|
||||
field_y_pos->setPlaceholderText( QString( "[-90...90]" ) );
|
||||
|
||||
if ( scale != 0 )
|
||||
field_scale->setText( QString::number( scale, 10 ) );
|
||||
else
|
||||
field_scale->setText( "1000000" );
|
||||
|
||||
QStringList acceptedFormats;
|
||||
acceptedFormats << "bmp" << "png" << "jpg";
|
||||
formatBox->addItems(acceptedFormats);
|
||||
|
||||
int idx = formatBox->findText(image_format);
|
||||
if (idx >= 0)
|
||||
formatBox->setCurrentIndex(idx);
|
||||
|
||||
urlEdit->setText( server_url );
|
||||
|
||||
/*
|
||||
* Acceptable longtitude range: [ -180 ... 180 ].
|
||||
*/
|
||||
longtitude_validator = new QDoubleValidator();
|
||||
longtitude_validator->setRange( -180.0, 180.0 );
|
||||
longtitude_validator->setDecimals( 8 );
|
||||
longtitude_validator->setNotation( QDoubleValidator::StandardNotation );
|
||||
field_x_pos->setValidator( longtitude_validator );
|
||||
|
||||
/*
|
||||
* Acceptable latitude range: [ -90 ... 90 ].
|
||||
*/
|
||||
latitude_validator = new QDoubleValidator();
|
||||
latitude_validator->setRange( -90.0, 90.0 );
|
||||
latitude_validator->setDecimals( 8 );
|
||||
latitude_validator->setNotation( QDoubleValidator::StandardNotation );
|
||||
field_y_pos->setValidator( latitude_validator );
|
||||
|
||||
/*
|
||||
* Acceptable width and height of resulting image in pixels: positive values.
|
||||
*/
|
||||
QIntValidator *pixel_validator = new QIntValidator();
|
||||
pixel_validator->setBottom( 1 );
|
||||
field_width->setValidator( pixel_validator );
|
||||
field_height->setValidator( pixel_validator );
|
||||
|
||||
/*
|
||||
* Setting up the left dock panel's vertical layout order.
|
||||
*/
|
||||
QVBoxLayout *vlayout = new QVBoxLayout();
|
||||
QHBoxLayout *hlayout = new QHBoxLayout();
|
||||
vlayout->addWidget( new QLabel( tr( "Image width (px):" ) ) );
|
||||
vlayout->addWidget( field_width );
|
||||
vlayout->addWidget( new QLabel( tr( "Image height (px):" ) ) );
|
||||
vlayout->addWidget( field_height );
|
||||
vlayout->addWidget( new QLabel( tr( "Longtitude:" ) ) );
|
||||
vlayout->addWidget( field_x_pos );
|
||||
vlayout->addWidget( new QLabel( tr( "Latitude:" ) ) );
|
||||
vlayout->addWidget( field_y_pos );
|
||||
vlayout->addWidget( new QLabel( tr( "Scale:" ) ) );
|
||||
hlayout->addWidget( new QLabel( "1:" ) );
|
||||
hlayout->addWidget( field_scale );
|
||||
vlayout->addLayout( hlayout );
|
||||
vlayout->addWidget( new QLabel( tr( "URL:" ) ) );
|
||||
vlayout->addWidget( urlEdit );
|
||||
vlayout->addWidget( new QLabel( tr( "Format:" ) ) );
|
||||
vlayout->addWidget(formatBox);
|
||||
vlayout->addWidget( request_btn );
|
||||
setLayout( vlayout );
|
||||
|
||||
connect( request_btn, SIGNAL( released() ),
|
||||
this, SLOT( request_btn_handler() ) );
|
||||
|
||||
/*
|
||||
* Go to the next input field if user pressed Enter in the previous one
|
||||
*/
|
||||
connect( field_width, SIGNAL( returnPressed() ),
|
||||
this, SLOT( go_to_field_height() ) );
|
||||
connect( field_height, SIGNAL( returnPressed() ),
|
||||
this, SLOT( go_to_field_x_pos() ) );
|
||||
connect( field_x_pos, SIGNAL( returnPressed() ),
|
||||
this, SLOT( go_to_field_y_pos() ) );
|
||||
connect( field_y_pos, SIGNAL( returnPressed() ),
|
||||
this, SLOT( go_to_field_scale() ) );
|
||||
connect( field_scale, SIGNAL( returnPressed() ),
|
||||
this, SLOT( go_to_request_btn() ) );
|
||||
request_btn->setDefault( true );
|
||||
|
||||
/*
|
||||
* Trying to imitate click
|
||||
*/
|
||||
if ( _consoleMode ) {
|
||||
request_btn_handler();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWidget::start_processing_request()
|
||||
{
|
||||
/*
|
||||
* Latitudes and longtitudes may have "Intermediate" validation state,
|
||||
* which means that if 180 is the greatest value that is acceptable,
|
||||
* user can't type in 1800, but he can type in 184, which is out of range.
|
||||
* This check prohibits invalid input.
|
||||
*
|
||||
* Width and height validation is omitted due to the fact that only
|
||||
* positive numbers are allowed.
|
||||
*
|
||||
* See: https://doc.qt.io/qt-5/qintvalidator.html#validate
|
||||
*/
|
||||
int cursor = 0;
|
||||
QString temp_x_pos = field_x_pos->text();
|
||||
QString temp_y_pos = field_y_pos->text();
|
||||
if ( longtitude_validator->validate( temp_x_pos, cursor ) != QValidator::Acceptable )
|
||||
{
|
||||
field_x_pos->clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if ( latitude_validator->validate( temp_y_pos, cursor ) != QValidator::Acceptable )
|
||||
{
|
||||
field_y_pos->clear();
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Request map rendering from server and wait for reply.
|
||||
*/
|
||||
process_request();
|
||||
}
|
||||
|
||||
void MainWidget::process_request()
|
||||
{
|
||||
double lon = field_x_pos->text().toDouble();
|
||||
double lat = field_y_pos->text().toDouble();
|
||||
uint w = field_width->text().toUInt();
|
||||
uint h = field_height->text().toUInt();
|
||||
uint scale = field_scale->text().toUInt();
|
||||
|
||||
QString format = formatBox->currentText();
|
||||
QString url = urlEdit->text();
|
||||
|
||||
qDebug() << "Sending request: ";
|
||||
qDebug() << "X:" << lon
|
||||
<< "Y:" << lat
|
||||
<< "Width:" << w
|
||||
<< "Height:" << h
|
||||
<< "Scale: 1 :" << scale
|
||||
<< "Format:" << format;
|
||||
|
||||
uint orderId = 0;
|
||||
char pinCode[128];
|
||||
int status = gis_map_server_request_map( url.toStdString().c_str(), lat, lon, scale, w, h, format.toStdString().c_str(), &orderId, pinCode );
|
||||
if ( status != 0 ) {
|
||||
qDebug() << "Failed to make request" << status;
|
||||
if ( _consoleMode ) {
|
||||
exit(1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "Client got id:" << orderId;
|
||||
qDebug() << "Client got pincode:" << pinCode;
|
||||
|
||||
status = gis_map_server_get_image( url.toStdString().c_str(), orderId, pinCode, &image );
|
||||
if ( status != 0 ) {
|
||||
qDebug() << "Failed to get image" << status;
|
||||
if ( _consoleMode ) {
|
||||
exit(1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
qDebug() << "Got image";
|
||||
|
||||
if ( _consoleMode ) {
|
||||
if ( image.save( _outputImageName ) )
|
||||
qDebug() << "Image was saved to current directory as " << _outputImageName;
|
||||
else
|
||||
qDebug() << "Failed to save image as " << _outputImageName;
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
emit request_completed( image );
|
||||
}
|
||||
|
||||
void MainWidget::set_UI_active( bool state )
|
||||
{
|
||||
field_x_pos->setEnabled( state );
|
||||
field_y_pos->setEnabled( state );
|
||||
field_width->setEnabled( state );
|
||||
field_height->setEnabled( state );
|
||||
field_scale->setEnabled( state );
|
||||
}
|
||||
|
||||
void MainWidget::request_btn_handler()
|
||||
{
|
||||
set_UI_active( false );
|
||||
start_processing_request();
|
||||
set_UI_active( true );
|
||||
}
|
72
src/gis-client-render/mainwidget.h
Обычный файл
72
src/gis-client-render/mainwidget.h
Обычный файл
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* (c) 2011-2022, SWD Embedded Systems Limited, http://www.kpda.ru
|
||||
*/
|
||||
|
||||
#ifndef MAINWIDGET_H
|
||||
#define MAINWIDGET_H
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
#include <QValidator>
|
||||
#include <QWidget>
|
||||
|
||||
class MainWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
explicit MainWidget( double x_pos,
|
||||
double y_pos,
|
||||
uint32_t width,
|
||||
uint32_t height,
|
||||
uint32_t scale,
|
||||
const char *server_url,
|
||||
const char *image_format,
|
||||
const char *outputImageName,
|
||||
bool consoleMode );
|
||||
|
||||
QImage image;
|
||||
|
||||
private:
|
||||
const bool _consoleMode;
|
||||
const char *_outputImageName;
|
||||
const char *_server_url;
|
||||
const char *_image_format;
|
||||
|
||||
void process_request();
|
||||
void set_UI_active( bool state );
|
||||
|
||||
bool request_btn_state;
|
||||
|
||||
QDoubleValidator *longtitude_validator;
|
||||
QDoubleValidator *latitude_validator;
|
||||
|
||||
QLineEdit *field_x_pos;
|
||||
QLineEdit *field_y_pos;
|
||||
QLineEdit *field_width;
|
||||
QLineEdit *field_height;
|
||||
QLineEdit *field_scale;
|
||||
QLineEdit *urlEdit;
|
||||
QComboBox *formatBox;
|
||||
QPushButton *request_btn;
|
||||
|
||||
signals:
|
||||
void request_completed( const QImage &img );
|
||||
void request_failed();
|
||||
public slots:
|
||||
|
||||
void start_processing_request();
|
||||
void request_btn_handler();
|
||||
|
||||
/*
|
||||
* Go to the next input field if user pressed Enter in the previous one
|
||||
*/
|
||||
void go_to_field_height() { field_height->setFocus( Qt::OtherFocusReason ); }
|
||||
void go_to_field_x_pos() { field_x_pos->setFocus( Qt::OtherFocusReason ); }
|
||||
void go_to_field_y_pos() { field_y_pos->setFocus( Qt::OtherFocusReason ); }
|
||||
void go_to_field_scale() { field_scale->setFocus( Qt::OtherFocusReason ); }
|
||||
void go_to_request_btn() { request_btn->setFocus( Qt::OtherFocusReason ); }
|
||||
};
|
||||
|
||||
#endif // MAINWIDGET_H
|
63
src/gis-client-render/mainwindow.cpp
Обычный файл
63
src/gis-client-render/mainwindow.cpp
Обычный файл
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* (c) 2011-2022, SWD Embedded Systems Limited, http://www.kpda.ru
|
||||
*/
|
||||
|
||||
#include "mainwindow.h"
|
||||
#include "mainwidget.h"
|
||||
|
||||
MainWindow::MainWindow( double x_pos,
|
||||
double y_pos,
|
||||
uint32_t width,
|
||||
uint32_t height,
|
||||
uint32_t scale,
|
||||
const char *server_url,
|
||||
const char *image_format,
|
||||
const char *outputImageName,
|
||||
bool consoleMode ) : QMainWindow()
|
||||
{
|
||||
/*
|
||||
* Left dock menu panel widget with request parameters
|
||||
*/
|
||||
QDockWidget *dock_widget = new QDockWidget( tr( "Request parameters" ), this );
|
||||
dock_widget->setAllowedAreas( Qt::LeftDockWidgetArea );
|
||||
dock_widget->setFeatures( QDockWidget::NoDockWidgetFeatures );
|
||||
|
||||
MainWidget *menu = new MainWidget( x_pos,
|
||||
y_pos,
|
||||
width,
|
||||
height,
|
||||
scale,
|
||||
server_url,
|
||||
image_format,
|
||||
outputImageName,
|
||||
consoleMode );
|
||||
dock_widget->setWidget( menu );
|
||||
addDockWidget( Qt::LeftDockWidgetArea, dock_widget );
|
||||
|
||||
/*
|
||||
* Resulting image viewport widget
|
||||
*/
|
||||
viewport = new ImageViewport();
|
||||
viewport->show();
|
||||
|
||||
connect( menu, SIGNAL( request_completed( const QImage&) ),
|
||||
viewport, SLOT( show_map_image( const QImage&) ) );
|
||||
|
||||
connect( menu, SIGNAL( request_failed() ),
|
||||
viewport, SLOT( clean_map_image() ) );
|
||||
|
||||
|
||||
setMinimumSize( 700, 400 );
|
||||
setWindowTitle( tr( "Cartographic client" ) );
|
||||
setCentralWidget( viewport );
|
||||
}
|
||||
|
||||
/*
|
||||
* Reimplementation of Qt's resizeEvent() for image fitting
|
||||
* in the viewport after each change of the window's size.
|
||||
*/
|
||||
void MainWindow::resizeEvent( QResizeEvent *event )
|
||||
{
|
||||
Q_UNUSED( event );
|
||||
viewport->fit_image();
|
||||
}
|
35
src/gis-client-render/mainwindow.h
Обычный файл
35
src/gis-client-render/mainwindow.h
Обычный файл
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* (c) 2011-2022, SWD Embedded Systems Limited, http://www.kpda.ru
|
||||
*/
|
||||
|
||||
#ifndef MAINWINDOW_H
|
||||
#define MAINWINDOW_H
|
||||
|
||||
#include <QDockWidget>
|
||||
#include <QMainWindow>
|
||||
#include <QWheelEvent>
|
||||
|
||||
#include "image_viewport.h"
|
||||
|
||||
class MainWindow : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MainWindow( double x_pos,
|
||||
double y_pos,
|
||||
uint32_t width,
|
||||
uint32_t height,
|
||||
uint32_t scale,
|
||||
const char *server_url,
|
||||
const char *image_format,
|
||||
const char *outputImageName,
|
||||
bool consoleMode );
|
||||
|
||||
private:
|
||||
ImageViewport *viewport;
|
||||
protected:
|
||||
virtual void resizeEvent( QResizeEvent *event );
|
||||
};
|
||||
|
||||
#endif // MAINWINDOW_H
|
156
src/gis-client-render/mapsender.cpp
Обычный файл
156
src/gis-client-render/mapsender.cpp
Обычный файл
@ -0,0 +1,156 @@
|
||||
/*
|
||||
* (c) 2011-2022, SWD Embedded Systems Limited, http://www.kpda.ru
|
||||
*/
|
||||
|
||||
#include "mapsender.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QBuffer>
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QImage>
|
||||
|
||||
#include "gismapserver.h"
|
||||
|
||||
int MapSender::sendRequest( double lat,
|
||||
double lon,
|
||||
int scale,
|
||||
int w,
|
||||
int h,
|
||||
const char* format,
|
||||
uint *orderId,
|
||||
char *pinCode )
|
||||
{
|
||||
QString requestUrl = _url + "/?";
|
||||
requestUrl += "&lat=" + QString::number(lat);
|
||||
requestUrl += "&lon=" + QString::number(lon);
|
||||
requestUrl += "&scale=" + QString::number(scale);
|
||||
requestUrl += "&w=" + QString::number(w);
|
||||
requestUrl += "&h=" + QString::number(h);
|
||||
requestUrl += "&format=" + QString(format);
|
||||
|
||||
QUrl url( requestUrl );
|
||||
QNetworkRequest request(url);
|
||||
request.setRawHeader( QByteArray("agent"), QByteArray("gis") );
|
||||
|
||||
QNetworkReply *reply = manager->get(request);
|
||||
waitForReplyFinished( reply, _timeout_ms );
|
||||
|
||||
QVariant statusCodeAttribute = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
|
||||
bool ok = false;
|
||||
GisHttpCodes statusCode = static_cast<GisHttpCodes>(statusCodeAttribute.toInt(&ok));
|
||||
if ( !ok || statusCode != GisHttpCodes::Ok ) {
|
||||
qDebug() << gis_map_server_get_error_message( statusCode );
|
||||
return EFAULT;
|
||||
}
|
||||
|
||||
QString orderId_tag = "orderId=";
|
||||
QString pinCode_tag = "pincode=";
|
||||
|
||||
QByteArray rawData = reply->readAll();
|
||||
QList<QByteArray> rawLines = rawData.split(',');
|
||||
|
||||
QString orderStr( rawLines[0] );
|
||||
if (orderStr.contains( orderId_tag )) {
|
||||
orderStr.remove(orderId_tag);
|
||||
*orderId = orderStr.toUInt( &ok );
|
||||
if ( !ok ) {
|
||||
qDebug() << "Invalid <orderId>";
|
||||
return EFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
QString pinCodeStr( rawLines[1] );
|
||||
if (pinCodeStr.contains( pinCode_tag )) {
|
||||
pinCodeStr.remove(pinCode_tag);
|
||||
pinCodeStr.remove( " " );
|
||||
strcpy( pinCode, pinCodeStr.toStdString().c_str() );
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int MapSender::getImageWithWaiting( char *pinCode, QImage *image )
|
||||
{
|
||||
uint waitMs = 100;
|
||||
QDateTime start = QDateTime::currentDateTimeUtc();
|
||||
QDateTime prevRequestTime = QDateTime::currentDateTimeUtc();
|
||||
QDateTime currentRequestTime = prevRequestTime;
|
||||
|
||||
int status = getImage( pinCode, image );
|
||||
while ( status == EAGAIN ) {
|
||||
qApp->processEvents( QEventLoop::AllEvents, waitMs );
|
||||
|
||||
currentRequestTime = QDateTime::currentDateTimeUtc();
|
||||
qint64 requestdeltaMs = prevRequestTime.msecsTo( currentRequestTime );
|
||||
if ( requestdeltaMs > 500 ) {
|
||||
prevRequestTime = currentRequestTime;
|
||||
status = getImage( pinCode, image );
|
||||
}
|
||||
|
||||
QDateTime current = QDateTime::currentDateTimeUtc();
|
||||
auto totalDeltaMs = start.msecsTo( current );
|
||||
if ( totalDeltaMs > _timeout_ms )
|
||||
return ETIMEDOUT;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
int MapSender::getImage( char *pinCode, QImage *image )
|
||||
{
|
||||
QString requestUrl = _url + "/?&orderId=" + QString::number(_orderId) + "&pincode=" + QString(pinCode);
|
||||
QUrl url( requestUrl );
|
||||
QNetworkRequest request(url);
|
||||
|
||||
QNetworkReply *reply = manager->get(request);
|
||||
waitForReplyFinished( reply, _timeout_ms );
|
||||
|
||||
QVariant statusCodeAttribute = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
|
||||
bool ok = false;
|
||||
int statusCode = statusCodeAttribute.toInt(&ok);
|
||||
if ( !ok || statusCode != GisHttpCodes::Ok ) {
|
||||
if ( statusCode == GisHttpCodes::IsProcessing ) {
|
||||
return EAGAIN;
|
||||
}
|
||||
|
||||
qDebug() << "statusCode" << statusCode;
|
||||
return EFAULT;
|
||||
}
|
||||
|
||||
if ( !reply->isFinished() )
|
||||
return EFAULT;
|
||||
|
||||
QVariant format_hdr = reply->header( QNetworkRequest::ContentTypeHeader );
|
||||
QString format_str = format_hdr.toString();
|
||||
QString image_format = format_str.remove( "image/" );
|
||||
|
||||
QByteArray ba = reply->readAll();
|
||||
image->loadFromData( ba, image_format.toLocal8Bit().data() );
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MapSender::waitForReplyFinished( QNetworkReply *reply, uint timeout_ms )
|
||||
{
|
||||
uint waitMs = 100;
|
||||
bool isTimeoutEnabled = false;
|
||||
if ( timeout_ms > 0 )
|
||||
isTimeoutEnabled = true;
|
||||
|
||||
QDateTime start = QDateTime::currentDateTimeUtc();
|
||||
qint64 totalDeltaMs = 0;
|
||||
|
||||
while ( !reply->isFinished() ) {
|
||||
qApp->processEvents( QEventLoop::AllEvents, waitMs );
|
||||
|
||||
if ( isTimeoutEnabled ) {
|
||||
QDateTime current = QDateTime::currentDateTimeUtc();
|
||||
totalDeltaMs = start.msecsTo( current );
|
||||
if ( totalDeltaMs > timeout_ms )
|
||||
{
|
||||
qDebug() << "Timeout" << totalDeltaMs;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
37
src/gis-client-render/mapsender.h
Обычный файл
37
src/gis-client-render/mapsender.h
Обычный файл
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* (c) 2011-2022, SWD Embedded Systems Limited, http://www.kpda.ru
|
||||
*/
|
||||
|
||||
#ifndef MAPSENDER_H
|
||||
#define MAPSENDER_H
|
||||
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkAccessManager>
|
||||
|
||||
class MapSender : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MapSender( const QString &server_address, const int userId, const uint timeout_ms ) :
|
||||
_url( server_address ),
|
||||
_orderId( userId ),
|
||||
_timeout_ms( timeout_ms )
|
||||
{
|
||||
manager = new QNetworkAccessManager();
|
||||
}
|
||||
|
||||
//Client calls
|
||||
int sendRequest(double lat, double lon, int scale, int w, int h, const char* format, uint* userId , char* pinCode);
|
||||
int getImageWithWaiting( char *pinCode, QImage *image );
|
||||
|
||||
private:
|
||||
int getImage( char *pinCode, QImage *image );
|
||||
void waitForReplyFinished( QNetworkReply *reply, uint timeout_ms );
|
||||
|
||||
const QString _url;
|
||||
const int _orderId;
|
||||
const uint _timeout_ms;
|
||||
QNetworkAccessManager *manager;
|
||||
};
|
||||
|
||||
#endif // MAPSENDER_H
|
8
src/gis-client-render/nto/Makefile
Исполняемый файл
8
src/gis-client-render/nto/Makefile
Исполняемый файл
@ -0,0 +1,8 @@
|
||||
LIST=CPU
|
||||
ifndef QRECURSE
|
||||
QRECURSE=recurse.mk
|
||||
ifdef QCONFIG
|
||||
QRDIR=$(dir $(QCONFIG))
|
||||
endif
|
||||
endif
|
||||
include $(QRDIR)$(QRECURSE)
|
8
src/gis-client-render/nto/arm/Makefile
Исполняемый файл
8
src/gis-client-render/nto/arm/Makefile
Исполняемый файл
@ -0,0 +1,8 @@
|
||||
LIST=VARIANT
|
||||
ifndef QRECURSE
|
||||
QRECURSE=recurse.mk
|
||||
ifdef QCONFIG
|
||||
QRDIR=$(dir $(QCONFIG))
|
||||
endif
|
||||
endif
|
||||
include $(QRDIR)$(QRECURSE)
|
1
src/gis-client-render/nto/arm/o.le.v7/Makefile
Исполняемый файл
1
src/gis-client-render/nto/arm/o.le.v7/Makefile
Исполняемый файл
@ -0,0 +1 @@
|
||||
include ../../../common.mk
|
8
src/gis-client-render/nto/e2k/Makefile
Исполняемый файл
8
src/gis-client-render/nto/e2k/Makefile
Исполняемый файл
@ -0,0 +1,8 @@
|
||||
LIST=VARIANT
|
||||
ifndef QRECURSE
|
||||
QRECURSE=recurse.mk
|
||||
ifdef QCONFIG
|
||||
QRDIR=$(dir $(QCONFIG))
|
||||
endif
|
||||
endif
|
||||
include $(QRDIR)$(QRECURSE)
|
1
src/gis-client-render/nto/e2k/o.le/Makefile
Исполняемый файл
1
src/gis-client-render/nto/e2k/o.le/Makefile
Исполняемый файл
@ -0,0 +1 @@
|
||||
include ../../../common.mk
|
8
src/gis-client-render/nto/mips/Makefile
Исполняемый файл
8
src/gis-client-render/nto/mips/Makefile
Исполняемый файл
@ -0,0 +1,8 @@
|
||||
LIST=VARIANT
|
||||
ifndef QRECURSE
|
||||
QRECURSE=recurse.mk
|
||||
ifdef QCONFIG
|
||||
QRDIR=$(dir $(QCONFIG))
|
||||
endif
|
||||
endif
|
||||
include $(QRDIR)$(QRECURSE)
|
1
src/gis-client-render/nto/mips/o.be/Makefile
Исполняемый файл
1
src/gis-client-render/nto/mips/o.be/Makefile
Исполняемый файл
@ -0,0 +1 @@
|
||||
include ../../../common.mk
|
8
src/gis-client-render/nto/ppc/Makefile
Исполняемый файл
8
src/gis-client-render/nto/ppc/Makefile
Исполняемый файл
@ -0,0 +1,8 @@
|
||||
LIST=VARIANT
|
||||
ifndef QRECURSE
|
||||
QRECURSE=recurse.mk
|
||||
ifdef QCONFIG
|
||||
QRDIR=$(dir $(QCONFIG))
|
||||
endif
|
||||
endif
|
||||
include $(QRDIR)$(QRECURSE)
|
1
src/gis-client-render/nto/ppc/o.be.spe/Makefile
Исполняемый файл
1
src/gis-client-render/nto/ppc/o.be.spe/Makefile
Исполняемый файл
@ -0,0 +1 @@
|
||||
include ../../../common.mk
|
1
src/gis-client-render/nto/ppc/o.be/Makefile
Исполняемый файл
1
src/gis-client-render/nto/ppc/o.be/Makefile
Исполняемый файл
@ -0,0 +1 @@
|
||||
include ../../../common.mk
|
8
src/gis-client-render/nto/x86/Makefile
Исполняемый файл
8
src/gis-client-render/nto/x86/Makefile
Исполняемый файл
@ -0,0 +1,8 @@
|
||||
LIST=VARIANT
|
||||
ifndef QRECURSE
|
||||
QRECURSE=recurse.mk
|
||||
ifdef QCONFIG
|
||||
QRDIR=$(dir $(QCONFIG))
|
||||
endif
|
||||
endif
|
||||
include $(QRDIR)$(QRECURSE)
|
1
src/gis-client-render/nto/x86/o/Makefile
Исполняемый файл
1
src/gis-client-render/nto/x86/o/Makefile
Исполняемый файл
@ -0,0 +1 @@
|
||||
include ../../../common.mk
|
5
src/gis-client-render/translations.qrc
Обычный файл
5
src/gis-client-render/translations.qrc
Обычный файл
@ -0,0 +1,5 @@
|
||||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>translations/gis-client-render_ru.qm</file>
|
||||
</qresource>
|
||||
</RCC>
|
56
src/gis-client-render/translations/gis-client-render_ru.ts
Обычный файл
56
src/gis-client-render/translations/gis-client-render_ru.ts
Обычный файл
@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.0" language="ru_RU">
|
||||
<context>
|
||||
<name>MainWidget</name>
|
||||
<message>
|
||||
<location filename="../mainwidget.cpp" line="12"/>
|
||||
<source>Longtitude:</source>
|
||||
<translation>Долгота:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../mainwidget.cpp" line="13"/>
|
||||
<source>Latitude:</source>
|
||||
<translation>Широта:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../mainwidget.cpp" line="14"/>
|
||||
<source>Image width (px):</source>
|
||||
<translation>Ширина изображения в пикс.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../mainwidget.cpp" line="15"/>
|
||||
<source>Image height (px):</source>
|
||||
<translation>Высота изображения в пикс.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../mainwidget.cpp" line="16"/>
|
||||
<source>Scale:</source>
|
||||
<translation>Масштаб:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../mainwidget.cpp" line="23"/>
|
||||
<location filename="../mainwidget.cpp" line="204"/>
|
||||
<source>Request map</source>
|
||||
<translation>Выполнить запрос</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../mainwidget.cpp" line="206"/>
|
||||
<source>Abort request</source>
|
||||
<translation>Прервать запрос</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
<message>
|
||||
<location filename="../mainwindow.cpp" line="31"/>
|
||||
<source>Cartographic client</source>
|
||||
<translation>Картографический клиент</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../mainwindow.cpp" line="12"/>
|
||||
<source>Request parameters</source>
|
||||
<translation>Параметры запроса</translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
2
src/gis-map-server/Makefile
Обычный файл
2
src/gis-map-server/Makefile
Обычный файл
@ -0,0 +1,2 @@
|
||||
LIST=OS
|
||||
include recurse.mk
|
6
src/gis-map-server/README.md
Обычный файл
6
src/gis-map-server/README.md
Обычный файл
@ -0,0 +1,6 @@
|
||||
## gis-map-server - карт-сервер, предназначенный для работы с ПК ЦКИ
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
![GIS Map Server IPC](docs/gis-map-server_IPC.drawio.png)
|
15
src/gis-map-server/START.sh
Исполняемый файл
15
src/gis-map-server/START.sh
Исполняемый файл
@ -0,0 +1,15 @@
|
||||
#!/bin/sh
|
||||
if [ -z "$GIS_ROOT" ]; then
|
||||
export GIS_ROOT=/opt/gis/
|
||||
fi
|
||||
export LD_LIBRARY_PATH=$GIS_ROOT/lib:$LD_LIBRARY_PATH
|
||||
export PATH=$GIS_ROOT/bin:$GIS_ROOT/sbin:$PATH
|
||||
export GIS_DEBUG_LEVEL=2
|
||||
export GIS_DISABLE_VERIFIER=y
|
||||
|
||||
slay -f gis-core
|
||||
gis-core -dsxf-local,sync=soft -ds57-local,sync=soft &
|
||||
waitfor /dev/gis_core 300
|
||||
|
||||
echo 'Starting server'
|
||||
$GIS_ROOT/data/resources/gis-map-server/server.py $GIS_ROOT/data/config/gis-map-server.conf
|
20
src/gis-map-server/common.mk
Обычный файл
20
src/gis-map-server/common.mk
Обычный файл
@ -0,0 +1,20 @@
|
||||
ifndef QCONFIG
|
||||
QCONFIG=qconfig.mk
|
||||
endif
|
||||
|
||||
include $(QCONFIG)
|
||||
|
||||
include $(MKFILES_ROOT)/qmacros.mk
|
||||
|
||||
NAME=$(notdir ${PROJECT_ROOT})
|
||||
|
||||
INSTALLDIR=/opt/gis/sbin/
|
||||
RESOURCESDIR=/opt/gis/data/resources/${NAME}
|
||||
LOGDIR=/opt/gis/data/config
|
||||
|
||||
PRE_INSTALL=$(CP_HOST) ${PROJECT_ROOT}/START.sh ${INSTALL_ROOT_nto}/${CPUVARDIR}/${INSTALLDIR}/${NAME}; $(CP_HOST) ${PROJECT_ROOT}/*.py ${INSTALL_ROOT_nto}/${CPUVARDIR}/${RESOURCESDIR}/
|
||||
POST_INSTALL=$(CP_HOST) -R ${PROJECT_ROOT}/html ${INSTALL_ROOT_nto}/${CPUVARDIR}/${RESOURCESDIR}; $(CP_HOST) ${PROJECT_ROOT}/$(NAME).conf ${INSTALL_ROOT_nto}/${CPUVARDIR}/${LOGDIR}/$(NAME).conf
|
||||
|
||||
ALL_DEPENDENCIES=Makefile
|
||||
|
||||
include $(MKFILES_ROOT)/qtargets.mk
|
1
src/gis-map-server/docs/architecture.drawio
Обычный файл
1
src/gis-map-server/docs/architecture.drawio
Обычный файл
@ -0,0 +1 @@
|
||||
<mxfile host="app.diagrams.net" modified="2021-12-21T13:21:52.932Z" agent="5.0 (X11)" etag="9BBCW-6gea-xDeGxZfHn" version="16.0.0" type="device"><diagram id="b_8fcSy7Od5qwKq7muho" name="Page-1">7Vhbb5swFP41PG7iEiA8hqTdHjatWnd9qhxwwZvBkXGadL9+tjHGBtpcVLXVNAkhczgc4+/77HNsJ1hW+3cUbMqPJIfY8d187wQrx/e9me874nLz+9Yyd5WhoChXTr3hGv2Byugq6xblsLEcGSGYoY1tzEhdw4xZNkAp2dlutwTbvW5AAUeG6wzgsfU7ylmpRuHHvf09REXZ9exFSfumAp2zGklTgpzsDFNw4QRLSghrW9V+CbEAr8Ol/e7ygbf6xyis2TEffLnJPq9XYEHd5nLbXCf4J/rxRkW5A3irBuysXCdZiHuayvtc3kN5Xzkrz5n78p6qYbH7Dqs7SBni0H0Aa4ivSIMYIjV/tSaMkcoJ0s5hgVEhXjCy4daSVZg/eLzJMdqIYNW+EHJ6uwYNyt5icA8pzG+oJDiV4uL6CFKyZRjVcKm5F0Y1It4V3D8IlacJ4MqFpIKM3nOXfSfS9gulWS8O2+ddrwDPVT6lwX6kbECJrtCRe154Q1FzAk3+mCY/whIMdMebhWgazLmSIbdji1su5D3o7Npn1nHM23HHNG97sr0wFJB0XfIRmL1O/0h62XWiw4Tqd9QHazoMMQz8r8orenX6Cib0NUAf1vlCrKf8qSY1FECBpoS5gtZAWdivAGOQ1tLiu4HAnlHyW6+hHJ2Ukm2dywA9rjAfLccHUTVQCydA62wUYsDQnR1+CknVwxVBvGNNmj+zSQviARkN2dIMqq/MdfhAoCGpDNACslEcyase9flUz/5TfYjqaER1eB7Vw0BR+KxUh4eptonZlYjB6w3IxNsdXyLHbGsVjEneQIr4L0MqIqC6UI5PsGCOEnK3OBp6CKYWTF1GPvmKGR2bkReelfz6WsrMyzy3usfnxemEO7crNN2nmedD1Vub4Y9Lu71EvMMSGaviKehPYpv+rvo286X/nPkyHrMvqA7t8qqlQ1OTGqVWouh4fWDrvKbA1vuXFwN7fuxUS5b2VkW3W/FH1qQ4aW6ZVbJnlLehsR+K7TmnaY+Najsy7POzC+GXF8lsFjzbjPzaQPpp/UtU4L6LRe1vzDhvBLq99LXjwhycku/8IbVPA2pQKUi/AYrAmgMs7Y/KMTGQUfI1uOGYsikClgQT2hdUtwjjgQmoXUvGOYB0YjtToTwX3UwyTrj3LZY1W8n9YP1Eq0EyXawaPM+n8u7pNPNHg+lTjjDcw2WOUdFmGDR81yeJAZSNzQZ155WtvGdZHz72z61fW/8dquBebMfjDTJBeO6OJxoEcp93y+NNnHK9doWERyrEf0mFDA8yguHyfqxC4mAQaFi3n60Q/tgfsbbu/UF1cPEX</diagram></mxfile>
|
Двоичные данные
src/gis-map-server/docs/architecture.drawio.png
Обычный файл
Двоичные данные
src/gis-map-server/docs/architecture.drawio.png
Обычный файл
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 15 KiB |
1
src/gis-map-server/docs/gis-map-server_IPC.drawio
Обычный файл
1
src/gis-map-server/docs/gis-map-server_IPC.drawio
Обычный файл
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Двоичные данные
src/gis-map-server/docs/gis-map-server_IPC.drawio.png
Обычный файл
Двоичные данные
src/gis-map-server/docs/gis-map-server_IPC.drawio.png
Обычный файл
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 90 KiB |
Двоичные данные
src/gis-map-server/docs/web_interface1.png
Обычный файл
Двоичные данные
src/gis-map-server/docs/web_interface1.png
Обычный файл
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 216 KiB |
6
src/gis-map-server/gis-map-server.conf
Обычный файл
6
src/gis-map-server/gis-map-server.conf
Обычный файл
@ -0,0 +1,6 @@
|
||||
SERVER_ADDRESS=localhost
|
||||
SERVER_PORT=8000
|
||||
SLOTS_NUMBER=2
|
||||
STORAGE_MAX_SIZE=6400000
|
||||
HTML_PAGES_PATH=./data/resources/gis-map-server/html/
|
||||
GIS_SHID=770
|
14
src/gis-map-server/html/order_request.html
Обычный файл
14
src/gis-map-server/html/order_request.html
Обычный файл
@ -0,0 +1,14 @@
|
||||
<html>
|
||||
<head>
|
||||
<title></title>
|
||||
<meta content="">
|
||||
<style></style>
|
||||
</head>
|
||||
<body>
|
||||
<center>
|
||||
<h2>Order is accepted</h2>
|
||||
<p>Order id = ORDERID, pincode = PIN_CODE</p>
|
||||
<a href='http://ADDRESS:PORT/?orderId=ORDERID&pincode=PIN_CODE'> Click here to obtain your order </a>
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
165
src/gis-map-server/html/start_page.html
Обычный файл
165
src/gis-map-server/html/start_page.html
Обычный файл
@ -0,0 +1,165 @@
|
||||
<html>
|
||||
<head>
|
||||
<title></title>
|
||||
<meta content="">
|
||||
<style>
|
||||
.params_frame {
|
||||
width: 200px;
|
||||
padding: 10px;
|
||||
float: left;
|
||||
border: 1px solid black;
|
||||
margin: 7px;
|
||||
}
|
||||
.results_frame {
|
||||
width: calc(100% - 288px);
|
||||
height: calc(100vh - 58px );
|
||||
float: left;
|
||||
border: 1px solid black;
|
||||
margin: 7px;
|
||||
overflow: scroll;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
function new_req() {
|
||||
|
||||
var lat = document.getElementById("lat").value;
|
||||
var lon = document.getElementById("lon").value;
|
||||
var lon = document.getElementById("lon").value;
|
||||
var scale = document.getElementById("scale").value;
|
||||
var h = document.getElementById("h").value;
|
||||
var w = document.getElementById("w").value;
|
||||
var format = document.getElementById("format").value;
|
||||
|
||||
document.getElementById('but').disabled = true;
|
||||
|
||||
if (document.contains(document.getElementById("frame_id"))) {
|
||||
document.getElementById("frame_id").remove();
|
||||
}
|
||||
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.id = "frame_id";
|
||||
iframe.scrolling = "no";
|
||||
iframe.style = "border: 0px;"
|
||||
document.getElementById('results').appendChild(iframe);
|
||||
|
||||
res = send_req(lat, lon, scale, w, h, format);
|
||||
|
||||
if (res[2] == '200')
|
||||
{
|
||||
show_content('Processing...')
|
||||
|
||||
counter = 0
|
||||
var interval_poll = window.setInterval(function()
|
||||
{
|
||||
answer = get_ord(res[0], res[1])
|
||||
|
||||
if (answer[1] == 200)
|
||||
{
|
||||
clearInterval(interval_poll);
|
||||
iframe.height = h;
|
||||
iframe.width = w;
|
||||
iframe.src = 'http://ADDRESS:PORT/?orderId=' + res[0] + '&pincode=' + res[1];
|
||||
document.getElementById('but').disabled = false;
|
||||
}
|
||||
else if (answer[1] == 202)
|
||||
{
|
||||
show_content('Processing... ' + answer[1])
|
||||
}
|
||||
else
|
||||
{
|
||||
clearInterval(interval_poll);
|
||||
iframe.srcdoc = answer[2];
|
||||
document.getElementById('but').disabled = false;
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
else
|
||||
{
|
||||
show_content('<p> <font color="red"> Error code obtained from server (' + res[2] + ') </font> </p>')
|
||||
document.getElementById('but').disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
function show_content(cont){
|
||||
document.getElementById('frame_id').src = "data:text/html;charset=utf-8," + escape(cont);
|
||||
}
|
||||
|
||||
function send_req(lat, lon, scale, w, h, format) {
|
||||
const url = 'http://ADDRESS:PORT/?lat='+ lat + '&lon=' + lon + '&scale=' + scale + '&w=' + w +'&h=' + h + '&format=' + format
|
||||
res = http_get(url)
|
||||
id = res[0].match('Order id = (.*?),')
|
||||
pincode = res[0].match('pincode = (.*?)<')
|
||||
if (id != null && pincode != null)
|
||||
{
|
||||
id = id[1]
|
||||
pincode = pincode[1]
|
||||
}
|
||||
return [id, pincode, res[1]];
|
||||
}
|
||||
|
||||
function get_ord(id, pincode) {
|
||||
const url = 'http://ADDRESS:PORT/?orderId=' + id + '&pincode=' + pincode
|
||||
return http_get(url);
|
||||
}
|
||||
|
||||
function http_get(url)
|
||||
{
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open( "GET", url, false);
|
||||
xhr.send( null );
|
||||
return [xhr.responseText, xhr.status, xhr.response]
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="params_frame">
|
||||
<form action="">
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="lat">Latitude</label>
|
||||
<td>
|
||||
<input name="lat" id="lat" size="7" value="55.4798">
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="lon">Longitude</label>
|
||||
<td>
|
||||
<input name="lon" id="lon" size="7" value="37.754">
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="scale">Scale</label>
|
||||
<td>
|
||||
<input name="scale" id="scale" size="7" value="10700">
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="w">Width</label>
|
||||
<td>
|
||||
<input name="w" id="w" size="7" value="1000">
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="h">Height</label>
|
||||
<td>
|
||||
<input name="h" id="h" size="7" value="1000">
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="format">Format</label>
|
||||
<td>
|
||||
<input name="format" id="format" size="7" value="png">
|
||||
</tr>
|
||||
</table>
|
||||
<BR>
|
||||
<div>
|
||||
<center> <input type="button" value="Send request" id="but" onclick="new_req()"> </center>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="results_frame" id="results">
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
324
src/gis-map-server/net_interface.py
Обычный файл
324
src/gis-map-server/net_interface.py
Обычный файл
@ -0,0 +1,324 @@
|
||||
###############################################################################
|
||||
# (c) 2011-2022, SWD Embedded Systems Limited, http://www.kpda.ru
|
||||
###############################################################################
|
||||
|
||||
#!/usr/bin/python3 -u
|
||||
import http.server, ssl
|
||||
import logging
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
from enum import Enum, unique
|
||||
from utils import Request_Status, get_log_path, get_status_desc
|
||||
|
||||
@unique
|
||||
class Page_Type(Enum):
|
||||
START = 0
|
||||
ORDER_REQUEST = 1
|
||||
BAD_REQUEST = 2
|
||||
|
||||
class Handler(http.server.BaseHTTPRequestHandler):
|
||||
"""
|
||||
A class for http-requests handling. Instance's lifetime is limited by the time of request processing.
|
||||
|
||||
Attributes:
|
||||
__________
|
||||
pipe_conn : multiprocessing.connection.Connection
|
||||
Pipe connection instance for interacting with Scheduler.
|
||||
buffer : Storage_Class instance
|
||||
Buffer is used to store data from POST-requests obtained by Renderer.
|
||||
html_path : str
|
||||
The path to a folder with html pages that are used to responde clients.
|
||||
|
||||
Methods:
|
||||
________
|
||||
get_html_content(page_type)
|
||||
Obtains the content of html page of specified type and returns it as a string.
|
||||
do_POST()
|
||||
Handles POST requests.
|
||||
do_GET()
|
||||
Handles GET requests.
|
||||
bad_request(code, exc="")
|
||||
Inserts a error message obtained by code and prints an html-page into wfile. Also supports printing an exception string.
|
||||
clear_pipe()
|
||||
Obtains all possible data from Pipe connection if it exists.
|
||||
"""
|
||||
|
||||
def __init__(self, pipe_conn, buffer, html_path, *args):
|
||||
"""
|
||||
Parameters:
|
||||
__________
|
||||
pipe_conn : multiprocessing.connection.Connection
|
||||
Pipe connection instance for interacting with Scheduler.
|
||||
buffer : Storage_Class instance
|
||||
Buffer is used to store data from POST-requests obtained by Renderer.
|
||||
html_path : str
|
||||
The path to a folder with html pages that are used to responde clients.
|
||||
"""
|
||||
self.pipe_conn = pipe_conn
|
||||
self.buffer = buffer
|
||||
self.html_path = html_path
|
||||
http.server.BaseHTTPRequestHandler.__init__(self, *args)
|
||||
|
||||
def get_html_content(self, page_type):
|
||||
"""Obtains the content of html page of specified type and returns it as a string.
|
||||
|
||||
Parameters:
|
||||
__________
|
||||
page_type : Page_Type Enum instance
|
||||
Type of page which should be read.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
Content of html page
|
||||
"""
|
||||
if page_type == Page_Type.START.value:
|
||||
with open(self.html_path + '/start_page.html') as f:
|
||||
content = f.read()
|
||||
content = content.replace('ADDRESS', str(self.server.server_name))
|
||||
content = content.replace('PORT', str(self.server.server_port))
|
||||
return content
|
||||
elif page_type == Page_Type.ORDER_REQUEST.value:
|
||||
with open(self.html_path + '/order_request.html') as f:
|
||||
content = f.read()
|
||||
content = content.replace('ADDRESS', str(self.server.server_name))
|
||||
content = content.replace('PORT', str(self.server.server_port))
|
||||
return content
|
||||
else:
|
||||
return 'BadPage'
|
||||
|
||||
def do_POST(self):
|
||||
""" Handles POST requests. """
|
||||
try:
|
||||
# trying to get payload
|
||||
logging.debug('Got POST-request')
|
||||
length = int(self.headers['Content-Length'])
|
||||
logging.debug('Got length')
|
||||
orderId = int(self.headers['orderId'])
|
||||
logging.debug('Got orderId')
|
||||
img_format = self.headers['Content-Type']
|
||||
logging.debug('Got img_format')
|
||||
payload = self.rfile.read(length)
|
||||
logging.debug('Got payload')
|
||||
|
||||
status_code, deleted_ids = self.buffer.push(orderId, payload, img_format, length)
|
||||
|
||||
#sending information to scheduler
|
||||
self.clear_pipe()
|
||||
self.pipe_conn.send((2, deleted_ids))
|
||||
if self.pipe_conn.poll(1):
|
||||
logging.debug('net_interface: got answer from scheduler')
|
||||
answer = self.pipe_conn.recv()
|
||||
if answer == False:
|
||||
self.bad_request(Request_Status.REQUEST_FAILED.value, "Scheduler could not delete previous ids from table")
|
||||
else:
|
||||
self.bad_request(Request_Status.TIMEOUT.value, "Scheduler did not responde")
|
||||
|
||||
if status_code == Request_Status.READY.value:
|
||||
logging.debug('Push success')
|
||||
self.send_response(Request_Status.READY.value, "Got payload")
|
||||
self.end_headers()
|
||||
self.wfile.write('Accepted'.encode())
|
||||
else:
|
||||
logging.debug('Error while pushing {status_code}')
|
||||
self.bad_request(status_code, "Id is busy")
|
||||
|
||||
except Exception as exc:
|
||||
try:
|
||||
self.bad_request(Request_Status.INVALID_PARAM.value, exc)
|
||||
except Exception as exc:
|
||||
logging.debug(f"Bad connection with client {exc}")
|
||||
|
||||
def bad_request(self, code, exc=""):
|
||||
""" Prints an html-page with error code into wfile and inserts there an error message obtained by code. Also supports printing an exception string.
|
||||
|
||||
Parameters:
|
||||
__________
|
||||
code : Request_Status Enum instance
|
||||
An error code to print.A string of thrown exception to print.
|
||||
exc : str
|
||||
A string of thrown exception to print.
|
||||
"""
|
||||
self.send_response(code)
|
||||
self.send_header("Content-type", "text/html")
|
||||
self.send_header("Access-Control-Allow-Origin", "*")
|
||||
self.end_headers()
|
||||
self.wfile.write('<html><head><meta charset="utf-8">'.encode())
|
||||
self.wfile.write('<title>Bad request</title></head>'.encode())
|
||||
self.wfile.write(f'<body><p>Bad request: {code}:{get_status_desc(code)} {exc}</p></body></html>'.encode())
|
||||
logging.debug('requested')
|
||||
|
||||
def clear_pipe(self):
|
||||
""" Obtains all possible data from Pipe connection if it exists. """
|
||||
while True:
|
||||
logging.debug('clear pipe start')
|
||||
if self.pipe_conn.poll():
|
||||
self.pipe_conn.recv()
|
||||
continue
|
||||
logging.debug('clear pipe finish')
|
||||
break
|
||||
|
||||
def do_GET(self):
|
||||
""" Handles POST requests. """
|
||||
try:
|
||||
#analyzing agent
|
||||
try:
|
||||
if 'gis' in dict(self.headers)['agent']:
|
||||
logging.debug('Agent: GIS')
|
||||
gis_agent = True
|
||||
except:
|
||||
logging.debug('Agent: Unknown')
|
||||
gis_agent = False
|
||||
|
||||
# analyzing of parameters
|
||||
fields = dict()
|
||||
param_line = re.sub('/[?]*[&]*', '', self.path)
|
||||
|
||||
if '&' in param_line:
|
||||
for p in param_line.split('&'):
|
||||
print(p.split('='))
|
||||
key, val = p.split('=')
|
||||
fields[key] = val
|
||||
logging.debug(f"I've got a GET request, fields = {fields} from {self.path}")
|
||||
|
||||
if len(fields) == 0:
|
||||
# start page request
|
||||
answer = self.get_html_content(Page_Type.START.value)
|
||||
self.send_response(Request_Status.READY.value)
|
||||
self.send_header("Content-type", "text/html")
|
||||
self.send_header("Access-Control-Allow-Origin", "*")
|
||||
self.end_headers()
|
||||
self.wfile.write(answer.encode())
|
||||
|
||||
elif 'orderId' not in fields:
|
||||
# first type of GET-request
|
||||
|
||||
#sending to scheduler new order
|
||||
self.clear_pipe()
|
||||
self.pipe_conn.send((0, fields))
|
||||
|
||||
# obtaining id from scheduler
|
||||
if self.pipe_conn.poll(1):
|
||||
logging.debug('net_interface: got from scheduler')
|
||||
container = self.pipe_conn.recv()
|
||||
logging.debug(f'OBTAINED {container}')
|
||||
id_pin, is_valid = container
|
||||
orderId, pincode = id_pin
|
||||
logging.debug(f'net_interface: got from scheduler {orderId}, {is_valid}, {pincode}')
|
||||
if is_valid:
|
||||
self.send_response(Request_Status.READY.value)
|
||||
logging.debug('net_interface: 0')
|
||||
self.send_header("Content-type", "text/html")
|
||||
self.send_header("Access-Control-Allow-Origin", "*")
|
||||
logging.debug('net_interface: 1')
|
||||
self.end_headers()
|
||||
logging.debug('net_interface: 2')
|
||||
if gis_agent:
|
||||
logging.debug('net_interface: 3')
|
||||
self.wfile.write(f'orderId={orderId}, pincode={pincode}'.encode())
|
||||
else:
|
||||
logging.debug('net_interface: 4')
|
||||
answer = self.get_html_content(Page_Type.ORDER_REQUEST.value)
|
||||
answer = answer.replace('ORDERID', str(orderId))
|
||||
answer = answer.replace('PIN_CODE', pincode)
|
||||
self.wfile.write(answer.encode())
|
||||
else:
|
||||
# internal error, bad request params
|
||||
self.bad_request(Request_Status.INVALID_PARAM.value)
|
||||
else:
|
||||
# timeout error
|
||||
self.bad_request(Request_Status.TIMEOUT.value)
|
||||
|
||||
|
||||
else:
|
||||
# second type of GET-request
|
||||
if 'pincode' not in fields:
|
||||
self.bad_request(Request_Status.INVALID_PARAM.value)
|
||||
else:
|
||||
# asking scheduler about status of order
|
||||
orderId = int(fields['orderId'])
|
||||
pincode = fields['pincode']
|
||||
self.clear_pipe()
|
||||
self.pipe_conn.send((1, (orderId, pincode)))
|
||||
|
||||
# obtaining id from scheduler
|
||||
if self.pipe_conn.poll(2):
|
||||
status = self.pipe_conn.recv()
|
||||
if status == Request_Status.READY.value:
|
||||
output_data, img_format = self.buffer.pop_by_id(orderId)
|
||||
self.send_response(Request_Status.READY.value)
|
||||
self.send_header("Content-type", img_format)
|
||||
self.send_header("Access-Control-Allow-Origin", "*")
|
||||
self.end_headers()
|
||||
self.wfile.write(output_data)
|
||||
else:
|
||||
logging.debug(f'status: {status}')
|
||||
self.bad_request(status)
|
||||
else:
|
||||
self.bad_request(Request_Status.TIMEOUT.value)
|
||||
|
||||
except Exception as exc:
|
||||
logging.debug("Error! {0}".format(exc))
|
||||
self.bad_request(Request_Status.REQUEST_FAILED.value, exc)
|
||||
|
||||
class Net_Interface():
|
||||
"""
|
||||
A class to provide net-interface of map-server. For now it start http server.
|
||||
|
||||
Attributes:
|
||||
__________
|
||||
host : str
|
||||
IP adress or host name for the net interface module.
|
||||
port : int
|
||||
Connection port for the net interface module.
|
||||
|
||||
Methods:
|
||||
________
|
||||
init_logging(page_type)
|
||||
Starts logging to the file, obtained by get_log_path.
|
||||
start_server()
|
||||
Starts http server using Handler class for requests handling.
|
||||
"""
|
||||
|
||||
def __init__(self, host, port):
|
||||
"""
|
||||
Parameters:
|
||||
__________
|
||||
host : str
|
||||
IP adress or host name for the net interface module.
|
||||
port : int
|
||||
Connection port for the net interface module.
|
||||
"""
|
||||
self.port = port
|
||||
self.host = host
|
||||
self.init_logging()
|
||||
|
||||
def init_logging(self):
|
||||
""" Starts logging to the file, obtained by get_log_path. """
|
||||
log_path = get_log_path()
|
||||
if log_path == None:
|
||||
logging.debug("Could not find GIS_ROOT environment variable")
|
||||
sys.exit(1)
|
||||
|
||||
log_path += '/server.log'
|
||||
logging.basicConfig(format='%(asctime)s %(levelname)-8s %(message)s', filename=log_path, encoding='utf-8', level=logging.DEBUG)
|
||||
|
||||
def start_server(self, pipe_conn, buffer, html_path):
|
||||
""" Starts http server using Handler class for requests handling.
|
||||
|
||||
Parameters:
|
||||
__________
|
||||
pipe_conn : multiprocessing.connection.Connection
|
||||
Pipe connection instance for interacting with Scheduler.
|
||||
buffer : Storage_Class instance
|
||||
Buffer is used to store data from POST-requests obtained by Renderer.
|
||||
html_path : str
|
||||
The path to a folder with html pages that are used to responde clients.
|
||||
"""
|
||||
def handler(*args):
|
||||
Handler(pipe_conn, buffer, html_path, *args)
|
||||
|
||||
self.net_server = http.server.HTTPServer((self.host, self.port), handler)
|
||||
logging.debug('net_server Started')
|
||||
self.net_server.serve_forever()
|
8
src/gis-map-server/nto/Makefile
Обычный файл
8
src/gis-map-server/nto/Makefile
Обычный файл
@ -0,0 +1,8 @@
|
||||
LIST=CPU
|
||||
ifndef QRECURSE
|
||||
QRECURSE=recurse.mk
|
||||
ifdef QCONFIG
|
||||
QRDIR=$(dir $(QCONFIG))
|
||||
endif
|
||||
endif
|
||||
include $(QRDIR)$(QRECURSE)
|
8
src/gis-map-server/nto/arm/Makefile
Обычный файл
8
src/gis-map-server/nto/arm/Makefile
Обычный файл
@ -0,0 +1,8 @@
|
||||
LIST=VARIANT
|
||||
ifndef QRECURSE
|
||||
QRECURSE=recurse.mk
|
||||
ifdef QCONFIG
|
||||
QRDIR=$(dir $(QCONFIG))
|
||||
endif
|
||||
endif
|
||||
include $(QRDIR)$(QRECURSE)
|
1
src/gis-map-server/nto/arm/o.le.v7/Makefile
Обычный файл
1
src/gis-map-server/nto/arm/o.le.v7/Makefile
Обычный файл
@ -0,0 +1 @@
|
||||
include ../../../common.mk
|
8
src/gis-map-server/nto/e2k/Makefile
Обычный файл
8
src/gis-map-server/nto/e2k/Makefile
Обычный файл
@ -0,0 +1,8 @@
|
||||
LIST=VARIANT
|
||||
ifndef QRECURSE
|
||||
QRECURSE=recurse.mk
|
||||
ifdef QCONFIG
|
||||
QRDIR=$(dir $(QCONFIG))
|
||||
endif
|
||||
endif
|
||||
include $(QRDIR)$(QRECURSE)
|
1
src/gis-map-server/nto/e2k/o.le/Makefile
Обычный файл
1
src/gis-map-server/nto/e2k/o.le/Makefile
Обычный файл
@ -0,0 +1 @@
|
||||
include ../../../common.mk
|
8
src/gis-map-server/nto/mips/Makefile
Исполняемый файл
8
src/gis-map-server/nto/mips/Makefile
Исполняемый файл
@ -0,0 +1,8 @@
|
||||
LIST=VARIANT
|
||||
ifndef QRECURSE
|
||||
QRECURSE=recurse.mk
|
||||
ifdef QCONFIG
|
||||
QRDIR=$(dir $(QCONFIG))
|
||||
endif
|
||||
endif
|
||||
include $(QRDIR)$(QRECURSE)
|
1
src/gis-map-server/nto/mips/o.be/Makefile
Исполняемый файл
1
src/gis-map-server/nto/mips/o.be/Makefile
Исполняемый файл
@ -0,0 +1 @@
|
||||
include ../../../common.mk
|
8
src/gis-map-server/nto/ppc/Makefile
Обычный файл
8
src/gis-map-server/nto/ppc/Makefile
Обычный файл
@ -0,0 +1,8 @@
|
||||
LIST=VARIANT
|
||||
ifndef QRECURSE
|
||||
QRECURSE=recurse.mk
|
||||
ifdef QCONFIG
|
||||
QRDIR=$(dir $(QCONFIG))
|
||||
endif
|
||||
endif
|
||||
include $(QRDIR)$(QRECURSE)
|
1
src/gis-map-server/nto/ppc/o.be.spe/Makefile
Обычный файл
1
src/gis-map-server/nto/ppc/o.be.spe/Makefile
Обычный файл
@ -0,0 +1 @@
|
||||
include ../../../common.mk
|
1
src/gis-map-server/nto/ppc/o.be/Makefile
Обычный файл
1
src/gis-map-server/nto/ppc/o.be/Makefile
Обычный файл
@ -0,0 +1 @@
|
||||
include ../../../common.mk
|
8
src/gis-map-server/nto/x86/Makefile
Обычный файл
8
src/gis-map-server/nto/x86/Makefile
Обычный файл
@ -0,0 +1,8 @@
|
||||
LIST=VARIANT
|
||||
ifndef QRECURSE
|
||||
QRECURSE=recurse.mk
|
||||
ifdef QCONFIG
|
||||
QRDIR=$(dir $(QCONFIG))
|
||||
endif
|
||||
endif
|
||||
include $(QRDIR)$(QRECURSE)
|
1
src/gis-map-server/nto/x86/o/Makefile
Обычный файл
1
src/gis-map-server/nto/x86/o/Makefile
Обычный файл
@ -0,0 +1 @@
|
||||
include ../../../common.mk
|
317
src/gis-map-server/scheduler.py
Обычный файл
317
src/gis-map-server/scheduler.py
Обычный файл
@ -0,0 +1,317 @@
|
||||
###############################################################################
|
||||
# (c) 2011-2022, SWD Embedded Systems Limited, http://www.kpda.ru
|
||||
###############################################################################
|
||||
|
||||
#!/usr/bin/python3 -u -B
|
||||
import time
|
||||
import os
|
||||
import logging
|
||||
import subprocess
|
||||
import string
|
||||
import random
|
||||
|
||||
from utils import Request_Status, get_log_path
|
||||
|
||||
|
||||
class Worker():
|
||||
"""
|
||||
A class for organizing the parallel execution of any processes with required number of slots.
|
||||
|
||||
Attributes:
|
||||
__________
|
||||
slots_num : int
|
||||
A total number of available slots.
|
||||
slots : array
|
||||
An array of processes.
|
||||
size : int
|
||||
Current number of executing processes.
|
||||
|
||||
Methods:
|
||||
________
|
||||
fill_slot(page_type)
|
||||
Adds new process to the array.
|
||||
free_slot(num)
|
||||
Removes a process from slot with index = num.
|
||||
check_free_slot()
|
||||
Checks if there is at least one free slot in processes array.
|
||||
is_busy()
|
||||
Checks if there is at least one active process.
|
||||
active_slots()
|
||||
Returns a list of elements and its indexes from processes array if they are active.
|
||||
"""
|
||||
|
||||
def __init__(self, slots_num):
|
||||
"""
|
||||
Parameters:
|
||||
__________
|
||||
slots_num : int
|
||||
A total number of available slots.
|
||||
"""
|
||||
self.slots_num = slots_num
|
||||
self.slots = [0] * self.slots_num
|
||||
self.size = 0
|
||||
|
||||
def fill_slot(self, element):
|
||||
""" Adds new process to the array.
|
||||
This function has to be called only after cheking free slot with "check_free_slot".
|
||||
|
||||
Parameters:
|
||||
__________
|
||||
element : any type
|
||||
New element to be add into the processes array.
|
||||
"""
|
||||
logging.debug(f'{self.slots}')
|
||||
for i in range(self.slots_num):
|
||||
if self.slots[i] == 0:
|
||||
self.slots[i] = element
|
||||
self.size += 1
|
||||
return
|
||||
raise IndexError
|
||||
|
||||
def free_slot(self, num):
|
||||
""" Removes a process from slot with index = num.
|
||||
|
||||
Parameters:
|
||||
__________
|
||||
num : int
|
||||
Index of removed slot.
|
||||
"""
|
||||
self.slots[num] = 0
|
||||
self.size -= 1
|
||||
|
||||
def check_free_slot(self):
|
||||
""" Checks if there is at least one free slot in processes array. """
|
||||
if self.size < self.slots_num:
|
||||
return True
|
||||
return False
|
||||
|
||||
def is_busy(self):
|
||||
""" Checks if there is at least one active process. """
|
||||
if self.size > 0:
|
||||
return True
|
||||
return False
|
||||
|
||||
def active_slots(self):
|
||||
""" Returns a list of elements and its indexes from processes array if they are active. """
|
||||
return [ (self.slots[i], i) for i in range(self.slots_num) if self.slots[i] != 0]
|
||||
|
||||
class Scheduler():
|
||||
"""
|
||||
A class for organizing orders execution, id and pin codes generation, saving orders return codes, orders validating.
|
||||
|
||||
Attributes:
|
||||
__________
|
||||
orders : dict
|
||||
A dictionary of all oders. Key: id, value: [parameters, return codes, pincode]
|
||||
queue : list
|
||||
A queue with new orders that have not been executed.
|
||||
counter : int
|
||||
Total number of orders.
|
||||
|
||||
Methods:
|
||||
________
|
||||
validator(params)
|
||||
Validates parameters of an order.
|
||||
generate_pincode(dictionary, size)
|
||||
Generates new pincode of length = size, using characters from dictionary string.
|
||||
add_order(params)
|
||||
Adds new order with parameters = params to orders and queue.
|
||||
check_order(id)
|
||||
Checks if order with id exists.
|
||||
start_scheduler(pipe_conn, host, port, slots_num)
|
||||
Executes scheduler, starts an infinite loop for listening pipe connection = pipe_conn and organizing orders execution.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.orders = dict()
|
||||
self.cached = dict()
|
||||
self.queue = list()
|
||||
self.counter = 0
|
||||
|
||||
def validator(self, params):
|
||||
""" Validates parameters of an order.
|
||||
|
||||
Parameters:
|
||||
__________
|
||||
params : list
|
||||
A list of parameters for validating.
|
||||
"""
|
||||
params_list = ['lat', 'lon', 'scale', 'w', 'h', 'format']
|
||||
for par in params_list:
|
||||
if par not in params:
|
||||
logging.debug(f'Bad params: no {par} in {params}')
|
||||
return False
|
||||
try:
|
||||
float(params['lon'])
|
||||
float(params['lat'])
|
||||
except ValueError:
|
||||
logging.debug(f'Bad params: lat, lan, w, h or scale is not float in {params}')
|
||||
return False
|
||||
|
||||
if not params['scale'].isdigit() or not params['w'].isdigit() or not params['h'].isdigit():
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def generate_pincode(self, dictionary, size):
|
||||
""" Generates new pincode of length = size, using characters from dictionary string.
|
||||
|
||||
Parameters:
|
||||
__________
|
||||
dictionary : str
|
||||
A string with characters that is used as a dictionary for pincode creating.
|
||||
size : int
|
||||
A number of characters in picode.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pincode
|
||||
Generated pincode
|
||||
"""
|
||||
pincode = ''.join(random.choice(dictionary) for x in range(size))
|
||||
return pincode
|
||||
|
||||
def add_order(self, params):
|
||||
""" Adds new order with parameters = params to orders and queue.
|
||||
|
||||
Parameters:
|
||||
__________
|
||||
params : list
|
||||
A list of parameters of a new order.
|
||||
|
||||
|
||||
Returns
|
||||
-------
|
||||
id
|
||||
Id for new order, equal to self.counter value
|
||||
pincode
|
||||
Generated pincode for new order
|
||||
"""
|
||||
self.counter += 1
|
||||
pincode = self.generate_pincode(string.digits + string.ascii_letters, 6)
|
||||
self.orders[self.counter] = [params, Request_Status.PROCESSING.value, pincode]
|
||||
|
||||
self.cached[tuple(params.values())] = self.counter
|
||||
self.queue.append(self.counter)
|
||||
return self.counter, pincode
|
||||
|
||||
def check_order(self, id):
|
||||
""" Checks if order with id exists. """
|
||||
return id in self.orders
|
||||
|
||||
|
||||
def start_scheduler(self, pipe_conn, host, port, slots_num, sharedMemoryId):
|
||||
""" Executes scheduler, starts an infinite loop for listening pipe connection = pipe_conn and organizing orders execution.
|
||||
|
||||
Parameters:
|
||||
__________
|
||||
pipe_conn : multiprocessing.connection.Connection
|
||||
Pipe connection instance for interacting with Net_Interface.
|
||||
host : str
|
||||
IP adress or host name for net interface.
|
||||
port : int
|
||||
Connection port for net interface.
|
||||
slots_num : int
|
||||
A total number of available slots.
|
||||
"""
|
||||
logging.debug('start')
|
||||
try:
|
||||
gis_path = os.environ['GIS_ROOT']
|
||||
util_path = gis_path + '/sbin/gis-buffer-renderer'
|
||||
logging.debug(f'Renderer path ={util_path}')
|
||||
except Exception as exc:
|
||||
logging.debug('Could not find GIS_ROOT')
|
||||
|
||||
worker = Worker(slots_num)
|
||||
|
||||
while True:
|
||||
# checking new data in Pipe
|
||||
if pipe_conn.poll():
|
||||
data = pipe_conn.recv()
|
||||
logging.debug(f'Scheduler got: {data}')
|
||||
|
||||
if data[0] == 0:
|
||||
# request for new order
|
||||
logging.debug(f'Scheduler new order {data[1]}')
|
||||
code = 0
|
||||
orderId = 0
|
||||
pincode = 0
|
||||
|
||||
if self.validator(data[1]):
|
||||
param_tuple = tuple(data[1].values())
|
||||
if param_tuple in self.cached:
|
||||
logging.debug(f'Order exist, cached data is used')
|
||||
orderId = self.cached[param_tuple]
|
||||
pincode = self.orders[self.cached[param_tuple]][2]
|
||||
else:
|
||||
orderId, pincode = self.add_order(data[1])
|
||||
code = 1
|
||||
pipe_conn.send(((orderId, pincode), code))
|
||||
elif data[0] == 1:
|
||||
# request to check order
|
||||
orderId, pincode = data[1]
|
||||
logging.debug(f'Scheduler checking order id={orderId}')
|
||||
if self.check_order(orderId):
|
||||
logging.debug(f'Status: {self.orders[orderId][1]}')
|
||||
if self.orders[orderId][2] == pincode:
|
||||
pipe_conn.send(self.orders[orderId][1])
|
||||
if self.orders[orderId][1] == Request_Status.READY.value:
|
||||
#self.orders[orderId][1] = Request_Status.DONE.value
|
||||
pass
|
||||
else:
|
||||
logging.debug(f'Bad pincode {pincode} (not {self.orders[orderId][2]}) for order with id={orderId}')
|
||||
pipe_conn.send(Request_Status.INVALID_PARAM.value)
|
||||
else:
|
||||
logging.debug(f'No order in scheduler with ID {orderId}')
|
||||
pipe_conn.send(Request_Status.INVALID_PARAM.value)
|
||||
elif data[0] == 2:
|
||||
deleted_ids = data[1]
|
||||
logging.debug(f'Scheduler deletes ids={deleted_ids}')
|
||||
for i in deleted_ids:
|
||||
self.cached.pop(tuple(self.orders[i][0].values()))
|
||||
self.orders.pop(i)
|
||||
try:
|
||||
self.queue.remove(i)
|
||||
except:
|
||||
pass
|
||||
pipe_conn.send(True)
|
||||
continue
|
||||
else:
|
||||
#print('No data for scheduler')
|
||||
pass
|
||||
|
||||
# checking queue
|
||||
|
||||
#if any of slots are free
|
||||
if worker.check_free_slot():
|
||||
#if current_order == False:
|
||||
if self.queue:
|
||||
current_order_id = self.queue.pop(0)
|
||||
logging.debug(f'Current order id {current_order_id}')
|
||||
params = self.orders[current_order_id][0]
|
||||
logging.debug(f'{params}')
|
||||
child = subprocess.Popen([util_path, f'-uhttp://{host}:{port}', f'-o{current_order_id}',f"-x{params['lon']}", f"-y{params['lat']}",
|
||||
f"-s{params['scale']}", f"-w{params['w']}", f"-h{params['h']}", f"-f{params['format']}", f"-e{Request_Status.RENDER_FAILED.value}", f"-d{sharedMemoryId}"])
|
||||
|
||||
worker.fill_slot((current_order_id, child))
|
||||
|
||||
logging.debug('popen')
|
||||
|
||||
# if some slots are busy
|
||||
# checking if order is ready
|
||||
if worker.is_busy():
|
||||
#if child.poll() != None:
|
||||
for slot, id in worker.active_slots():
|
||||
if slot[1].poll() != None:
|
||||
print('order status ready', Request_Status.READY.value, type(Request_Status.READY.value))
|
||||
logging.debug(f'Scheduler detects process as ready, return code: {slot[1].returncode}, order status ready {Request_Status.READY.value}')
|
||||
if slot[1].returncode == 200:
|
||||
self.orders[slot[0]][1] = Request_Status.READY.value
|
||||
elif slot[1].returncode == Request_Status.NOMEM.value:
|
||||
self.orders[slot[0]][1] = Request_Status.NOMEM.value
|
||||
else:
|
||||
self.orders[slot[0]][1] = Request_Status.RENDER_FAILED.value
|
||||
worker.free_slot(id)
|
||||
time.sleep(0.1)
|
||||
return 2
|
156
src/gis-map-server/server.py
Исполняемый файл
156
src/gis-map-server/server.py
Исполняемый файл
@ -0,0 +1,156 @@
|
||||
###############################################################################
|
||||
# (c) 2011-2022, SWD Embedded Systems Limited, http://www.kpda.ru
|
||||
###############################################################################
|
||||
|
||||
#!/usr/bin/python3 -uB
|
||||
from net_interface import Net_Interface
|
||||
from scheduler import Scheduler
|
||||
from storage import Storage
|
||||
from utils import get_log_path
|
||||
|
||||
import argparse
|
||||
import shutil
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from multiprocessing import Process, Pipe
|
||||
import subprocess
|
||||
|
||||
class Server:
|
||||
"""
|
||||
A class used to represent gis-map-server instance consisting of instances of Interface_Class, Storage_Class and Scheduler_Class classes.
|
||||
|
||||
Attributes:
|
||||
__________
|
||||
host : str
|
||||
IP adress or host name for the net interface module.
|
||||
port : int
|
||||
Connection port for the net interface module.
|
||||
buf_size : int
|
||||
Maximum buffer size in bytes.
|
||||
slots_num : int
|
||||
A total number of available slots.
|
||||
html_path : str
|
||||
The path to a folder with html pages that are used to responde clients.
|
||||
|
||||
Methods:
|
||||
________
|
||||
server_init(Interface_Class, Storage_Class, Scheduler_Class)
|
||||
Initializes server with instances of Interface_Class, Storage_Class and Scheduler_Class classes.
|
||||
start()
|
||||
Starts server execution: executes scheduler as a subprocess, executes Interface_Class listening.
|
||||
"""
|
||||
|
||||
def __init__(self, host, port, slots_num, buf_size, html_path, sharedMemoryId):
|
||||
"""
|
||||
Parameters:
|
||||
__________
|
||||
host : str
|
||||
IP adress or host name for the net interface module.
|
||||
port : int
|
||||
Connection port for the net interface module.
|
||||
buf_size : int
|
||||
Maximum buffer size in bytes.
|
||||
slots_num : int
|
||||
A total number of available slots.
|
||||
html_path : str
|
||||
The path to a folder with html pages that are used to responde clients.
|
||||
"""
|
||||
self.port = port
|
||||
self.host = host
|
||||
self.buf_size = buf_size
|
||||
self.slots_num = slots_num
|
||||
self.html_path = html_path
|
||||
self.sharedMemoryId = sharedMemoryId
|
||||
|
||||
def server_init(self, Interface_Class, Storage_Class, Scheduler_Class):
|
||||
""" Initializes server with instances of Interface_Class, Storage_Class and Scheduler_Class classes.
|
||||
|
||||
Parameters:
|
||||
__________
|
||||
Interface_Class : class
|
||||
A class link to create net_interface.
|
||||
Storage_Class : class
|
||||
A class link to create storage.
|
||||
Scheduler_Class : class
|
||||
A class link to create scheduler.
|
||||
"""
|
||||
self.net_interface = Interface_Class(self.host, self.port)
|
||||
self.buf_storage = Storage_Class(self.buf_size)
|
||||
self.scheduler = Scheduler_Class()
|
||||
|
||||
def start(self):
|
||||
""" Starts server execution: executes scheduler as a subprocess, executes Interface_Class listening. Creates Pipe between two processes."""
|
||||
sched_conn, serv_conn = Pipe()
|
||||
sched = Process(target=self.scheduler.start_scheduler, args=(sched_conn, self.host, self.port, self.slots_num, self.sharedMemoryId))
|
||||
sched.start()
|
||||
|
||||
# Net_Interface start with access to Buf_Storage and Scheduler
|
||||
self.net_interface.start_server(serv_conn, self.buf_storage, self.html_path)
|
||||
|
||||
def parse_args():
|
||||
""" Function for parsing program arguments """
|
||||
parser = argparse.ArgumentParser(description='gis-map-server description:')
|
||||
parser.add_argument('config_path', type=str,
|
||||
help='a required string positional argument, path to gis-map-server config')
|
||||
args = parser.parse_args()
|
||||
return args
|
||||
|
||||
def parse_config(path):
|
||||
""" Function parses config and return values of required options """
|
||||
with open(path) as f:
|
||||
content = f.readlines()
|
||||
options = dict()
|
||||
for line in content:
|
||||
key, val = line.split('=')
|
||||
options[key] = val.strip()
|
||||
return options['SERVER_ADDRESS'], int(options['SERVER_PORT']), int(options['SLOTS_NUMBER']), int(options['STORAGE_MAX_SIZE']), options['HTML_PAGES_PATH'], options['GIS_SHID']
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
args = parse_args()
|
||||
except Exception as exp:
|
||||
print(f"Arguments parsing error: {exp}")
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
address, port, slots_num, buf_size, html_path, sharedMemoryId = parse_config(args.config_path)
|
||||
except Exception as exp:
|
||||
print(f"Config parsing error {exp}")
|
||||
sys.exit(1)
|
||||
|
||||
# log folder (re)creating
|
||||
log_path = get_log_path()
|
||||
if log_path == None:
|
||||
print("Could not find GIS_ROOT environment variable")
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
shutil.rmtree(log_path)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
except Exception as exc:
|
||||
print("Could not get an access to log folder", log_path, exc)
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
os.mkdir(log_path)
|
||||
except Exception as exc:
|
||||
print("Could not create log folder", log_path, exc)
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
subprocess.run(["gis-control", f'-s{sharedMemoryId}'])
|
||||
except Exception as exc:
|
||||
print("Could not make data request for sharedMemoryId", sharedMemoryId, exc)
|
||||
sys.exit(1)
|
||||
|
||||
# starting the server
|
||||
try:
|
||||
new_server = Server(address, port, slots_num, buf_size, os.environ['GIS_ROOT'] + '/' + html_path, sharedMemoryId)
|
||||
new_server.server_init(Net_Interface, Storage, Scheduler)
|
||||
new_server.start()
|
||||
except KeyboardInterrupt:
|
||||
print('Server is closed')
|
||||
sys.exit(0)
|
89
src/gis-map-server/storage.py
Обычный файл
89
src/gis-map-server/storage.py
Обычный файл
@ -0,0 +1,89 @@
|
||||
###############################################################################
|
||||
# (c) 2011-2022, SWD Embedded Systems Limited, http://www.kpda.ru
|
||||
###############################################################################
|
||||
|
||||
#!/usr/bin/python3 -uB
|
||||
from utils import Request_Status
|
||||
|
||||
class Storage():
|
||||
"""
|
||||
A class used to represent data storage for buffers obtained from Renderer.
|
||||
From the storage data is popped from Iterface class when it is requested by the client.
|
||||
|
||||
Attributes:
|
||||
__________
|
||||
storage : dict
|
||||
A dictionary used to store buffers. Key: id, val: (data, img_format).
|
||||
current_size : int
|
||||
Current size of buffer in bytes. It counts only size of data, not the dictionary element.
|
||||
buf_size : int
|
||||
Maximum buffer size in bytes.
|
||||
|
||||
Methods:
|
||||
________
|
||||
push(id, data, img_format, img_length)
|
||||
Pushes data to the storage.
|
||||
pop_by_id(id)
|
||||
Pops data by id.
|
||||
"""
|
||||
def __init__(self, buf_size):
|
||||
self.storage = dict()
|
||||
self.current_size = 0
|
||||
self.buf_size = buf_size
|
||||
|
||||
def push(self, id, data, img_format, img_length):
|
||||
"""Pushes data to the storage.
|
||||
|
||||
Parameters:
|
||||
__________
|
||||
id : int
|
||||
An id of the order, whose data will be stored.
|
||||
data : byte str
|
||||
Data to store in the storage.
|
||||
img_format : str
|
||||
A string which specifies a format of data.
|
||||
img_length : int
|
||||
Data size in bytes.
|
||||
|
||||
Returns
|
||||
-------
|
||||
status
|
||||
New order status after pushing the data
|
||||
"""
|
||||
if id in self.storage:
|
||||
return Request_Status.INVALID_PARAM.value
|
||||
|
||||
if img_length > self.buf_size:
|
||||
return Request_Status.NOMEM.value
|
||||
|
||||
deleted_ids = list()
|
||||
if self.buf_size < (self.current_size + img_length):
|
||||
keys = list(self.storage.keys())
|
||||
for i in keys:
|
||||
d = self.storage.pop(i)
|
||||
deleted_ids.append(i)
|
||||
self.current_size -= len(d[0])
|
||||
if self.buf_size >= (self.current_size + img_length):
|
||||
break
|
||||
|
||||
self.storage[id] = (data, img_format)
|
||||
self.current_size += img_length
|
||||
return Request_Status.READY.value, deleted_ids
|
||||
|
||||
def pop_by_id(self, id):
|
||||
"""
|
||||
Pops data by id.
|
||||
|
||||
Parameters:
|
||||
__________
|
||||
id : int
|
||||
An id of the order, whose data is stored.
|
||||
|
||||
Returns
|
||||
-------
|
||||
data
|
||||
Data popped by id
|
||||
"""
|
||||
data = self.storage[id]
|
||||
#self.current_size -= len(data[0])
|
||||
return data
|
51
src/gis-map-server/utils.py
Обычный файл
51
src/gis-map-server/utils.py
Обычный файл
@ -0,0 +1,51 @@
|
||||
###############################################################################
|
||||
# (c) 2011-2022, SWD Embedded Systems Limited, http://www.kpda.ru
|
||||
###############################################################################
|
||||
|
||||
#!/usr/bin/python3 -uB
|
||||
from enum import Enum, unique
|
||||
import os
|
||||
|
||||
@unique
|
||||
class Request_Status(Enum):
|
||||
# statuses below are saved inside of Scheduler order table
|
||||
READY = 200
|
||||
PROCESSING = 202
|
||||
INVALID_PARAM = 400
|
||||
DONE = 410
|
||||
NOMEM = 418
|
||||
RENDER_FAILED = 500
|
||||
|
||||
# codes below are used only to responde to the client
|
||||
TIMEOUT = 408
|
||||
REQUEST_FAILED = 520
|
||||
|
||||
def get_status_desc(status):
|
||||
"""Function returns a description for an error code."""
|
||||
if status == Request_Status.READY.value:
|
||||
return 'Request is ready'
|
||||
elif status == Request_Status.PROCESSING.value:
|
||||
return 'Request is processing'
|
||||
elif status == Request_Status.INVALID_PARAM.value:
|
||||
return 'Request has invallid parameters'
|
||||
elif status == Request_Status.DONE.value:
|
||||
return 'Request has been already obtained'
|
||||
elif status == Request_Status.NOMEM.value:
|
||||
return 'Request is not ready - not enough memory on server'
|
||||
elif status == Request_Status.RENDER_FAILED.value:
|
||||
return 'Request is failed - renderer did not finish successfully'
|
||||
elif status == Request_Status.TIMEOUT.value:
|
||||
return 'Request status is unknown, timeout error'
|
||||
elif status == Request_Status.REQUEST_FAILED.value:
|
||||
return 'Request is failed'
|
||||
else:
|
||||
return 'Unknown Error'
|
||||
|
||||
def get_log_path():
|
||||
"""Function returns the log path for gis-map-server."""
|
||||
try:
|
||||
path = os.environ['GIS_ROOT']
|
||||
path += '/data/logs/gis-map-server/'
|
||||
except:
|
||||
path = None
|
||||
return path
|
Загрузка…
Ссылка в новой задаче
Block a user