<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>نوشته های علیرضا توفیقی محمدی</title>
        <link>https://virgool.io/feed/@tofighi</link>
        <description>دانشجوی علوم کامپیوتر صنعتی شریف، CTO در نقشه و مسیریاب بلد، بلاگ: https://alireza.atofighi.ir</description>
        <language>fa</language>
        <pubDate>2026-04-15 10:27:16</pubDate>
        <image>
            <url>https://files.virgool.io/upload/users/7552/avatar/avatar.png?height=120&amp;width=120</url>
            <title>علیرضا توفیقی محمدی</title>
            <link>https://virgool.io/@tofighi</link>
        </image>

                    <item>
                <title>فلاتر و GRPC، بررسی عملی روی پلتفرم اندروید</title>
                <link>https://virgool.io/flutter-community/flutter-grpc-implementation-for-android-platform-todik8vwzs70</link>
                <description>Flutter + gRPCبه نام خدادر این مطلب، قصد داریم روش استفاده از GRPC در فلاتر را به شکل مختصر و با نمونه کد توضیح دهیم. کدها روی اندروید تست شدند ولی احتمالا روی پلتفرم‌های غیر از وب باید کار کنند. این مطلب را برای تحقیق درس برنامه‌نویسی موبایل دانشگاه صنعتی شریف آماده کرده‌ایم.جی‌آر‌پی‌سی چیست؟قبل از شروع مطلب اصلی، کمی درباره‌ی grpc صحبت کنیم. gRPC، یک پروتوکل برای انجام «فراخوانی رویه‌ای دوردست / Remote procedure call» است. به طوری که پیاده‌سازی آن به گونه‌ای است که اولا performance خیلی بالایی دارد و همچنین می‌تواند ارتباط بین هر دو محیطی یا زبان برنامه‌نویسی‌ای را برقرار کند. از مزیت‌های gRPC می‌توان به performance بالای آن در مقابل راه حل‌هایی مثل استفاده از داده‌های JSON‌ای و REST، اجبار در تعریف شدن پیام‌ها، ارتباط دو طرفه و به صورت stream در کنار فراخوانی‌های معمولی نام برد.برای اطلاعات بیشتر لینک روبرو را بخوانید: https://grpc.io/docs/what-is-grpc/introduction/فرایند کاردر gRPC، ابتدا نوع و فرمت پیام‌ها را در قالب فایل‌های proto تعریف می‌کنیم، این فایل‌ها شامل اطلاعات جزئیات کل پیام‌ها، اطلاعات درخواست‌ها و پاسخ آن و سرویس‌ها که هر سرویس مجموعه‌ای از rpcهاست می‌شود. بعد از تعریف proto، باید با کمک کامپایلر پروتوباف، فایل‌های پروتو‌ای که ساختیم را به کدهایی که برای زبانی که می‌خواهیم در آن برنامه بنویسیم تبدیل کنیم، در اینجا چون flutter را انتخاب کردیم، زبانمان dart می‌شود و خوش‌بختانه grpc، زبان dart را هم پشتیبانی می‌کند.بعد از کامپایل پروتو‌ها به زبان دارت، یک سرور می‌نویسیم که بیزینس لاجیک برنامه‌مان را شامل شود، سپس با فلاتر، یک کلاینت gRPC می‌سازیم که با سرور ارتباط برقرار کند.انواع RPCها در gRPCبر اساس اینکه request و response از نوع یک پیام یا یک stream باشند، ما ۴ نوع RPC در gRPC داریم:Unary: که یک درخواست و پاسخ ساده است.Server streaming: کلاینت یک درخواست می‌فرستد، اما سرور یک جریان از پاسخ‌ها بر می‌گرداند.Client streaming: کلاینت یک جریان از درخواست‌ها می‌فرستند و در پایان سرور یک پاسخ بر می‌گرداند.Bidirectional streaming: ارتباط دو طرفه که هر کدام یک جریان می‌فرستند و می‌تواند پاسخ و دریافتی و ... باشد.برای اطلاعات بیشتر لینک روبرو را بخوانید: https://grpc.io/docs/what-is-grpc/core-concepts/شروع با ساختن یک پروژه‌ی فلاترابتدا با کمک android studio یک پروژه بسازید، برای اینکار ابتدا مطمئن شوید که sdk مربوط به flutter و پلاگین‌های dart و flutter روی اندروید استودیو نصب باشد، سپس از منوی File، گزینه‌ی New Flutter project را انتخاب کنید، نوع پروژه را Flutter application انتخاب کنید و فرایند را ادامه دهید.بعد از ساخته شدن پروژه، کدهای فلاترمان در پوشه‌ی lib  قرار دارند.تعریف protoدر این مقاله، می‌خواهیم از هر کدام از این انواع یک تست انجام دهیم. چون هدفمان نوشتن یک بیزینس واقعی نیست، فرض کنید سرور ما، سروری برای تولید عدد تصادفی بین بازه‌ی minimum تا maximum است. ساده‌ترین درخواست به این سرور این است که یک بازه به آن بدهیم و سرور یک خروجی به ما بدهد که به نوع Unary می‌شود. درخواست دوم به این صورت است که یک بازه به سرور بدهیم، سپس سرور تا زمانی که درخواست باز است، هر ثانیه یک عدد تصادفی به کلاینت بفرستد. روش سوم، به این صورت است که کلاینت، یک جریان از بازه‌ها به سرور بفرستد، سپس سرور یک عدد تصادفی تولید کند که در همه‌ی بازه‌ها صدق کند.مدل آخر مثلا فرض کنید کلاینت هر یک ثانیه یک‌بار، درخواست جدید می‌دهد و سرور برای هر درخواست، ۳ عدد تصادفی از آن مدل تولید می‌کند.فایل پروتوی چنین کاری، کد زیر می‌شود:syntax = &amp;quotproto3&amp;quot

package random_exchange;

service RandomExchangerService {
    rpc GetSingleRandom (RandomRequest) returns (RandomResponse);
    rpc GetRandomForEver (RandomRequest) returns (stream RandomResponse);
    rpc GetSingleRandomWithMultipleConditions (stream RandomRequest) returns (RandomResponse);
    rpc GetBidiRandom(stream RandomRequest) returns (stream RandomResponse);
}

message RandomRequest {
    int32 minimum = 1;
    int32 maximum = 2;
}

message RandomResponse {
    int32 value = 1;
}همان‌طور که می‌بینید، دو نوع پیام داریم، یک پیام RandomRequest که بازه‌ی درخواست عدد تصادفی را مشخص کرده و RandomResponse که فقط یک عدد که آن عدد تصادفی است در آن است.سرویس RandomExchangerService شامل چهار rpc است که همان چهار فراخوانی‌ای است که در بالا توضیح دادیم.این فایل را در پوشه‌ی protos به نام random_exchange.proto قرار دهید.کامپایل پروتو‌ها به زبان Dartقبل از هر چیز، کامپایلر پروتوباف را نصب کنیم، برای این کار مثلا در ubuntu با کامند زیر می‌توانیم نصب را انجام دهیم:sudo apt install protobuf-compilerاین کامپایلر، به شکل پیش‌فرض کامپایل به زبان c را پشتیبانی می‌کند، اما برای اینکه بتوانیم به زبان dart کامپایل کنیم، باید یک پلاگین روی آن نصب کنیم.برای این کار، می‌توانیم از کامند زیر استفاده کنیم: (از تحریم شکن مناسب استفاده کنیم، چراکه به لطف گوگل تحریمیم! :| )dart pub global activate protoc_pluginاین کامند، پکیج protoc_plugin را به شکل گلوبال روی سیستم فعال می‌کند، اما فایل‌های bin آن به صورت پیش‌فرض در مسیر environmentـِ PATH قرار ندارد و سیستم آن را نمی‌شناسد. در warningهای دستور بالا، پوشه‌ای که باید به PATH اضافه شود ذکر می‌شود، در کل باید دستور زیر را در محیط لینوکسی اجرا کنید:export PATH=&amp;quot$PATH:$HOME/.pub-cache/bin&amp;quotپلاگین نصب شد! فرض کنید فایل protoی بخش قبل را در پوشه‌ی protos، به نام random_exchange.proto ذخیره کردیم و کدهای flutterـمان را در پوشه‌ی lib می‌نویسیم، در این پوشه یک پوشه‌ی دیگر به نام generated می‌سازیم که کدهای کامپایل شده در آن قرار گیرند و سپس فرایند کامپایل را انجام می‌دهیم:mkdir -p lib/generated
protoc --dart_out=grpc:lib/generated -Iprotos protos/random_exchange.protoبعد از اجرای دستور بالا، باید تعدادی فایل در پوشه‌ی generated ساخته شده باشند.اضافه کردن پیش‌نیاز‌هاقبل از ادامه، نیاز به اضافه کردن تعدادی پیش‌نیاز داریم که grpc به درستی کار کند، برای اینکار فایل pubspec.yaml را باز کنید و نیازمندی‌های زیر را به dependencies اضافه کنید:archive: ^3.0.0
async: ^2.5.0
crypto: ^3.0.0
fixnum: ^1.0.0
googleapis_auth: ^1.1.0
meta: ^1.3.0
http: ^0.13.0
http2: ^2.0.0
protobuf: ^2.0.0
grpc: ^3.0.0و همچنین نیازمندی‌های زیر را به dev_dependencies اضافه کنید:mockito: ^5.0.0
path: ^1.8.0
test: ^1.16.0
stream_channel: ^2.1.0
stream_transform: ^2.0.0البته همه‌ی این نیازمندی‌ها در این آموزش استفاده نمی‌شوند، اما در ادامه برای توسعه‌ی کدتان به آن‌ها نیاز پیدا خواهید کرد.حال با اجرای &#x60;flutter pub get&#x60; یا &#x60;dart pub get&#x60; نیازمندی‌ها را دانلود کنید. (تحریمیم، از تحریم شکن مناسب استفاده کنید :) )پیاده‌سازی سرورحال به سراغ پیاده‌سازی سرور برویم. سرور را در این مثال با dart پیاده‌سازی می‌کنیم، ولی می‌تواند با هر زبانی پیاده‌سازی شود.قبل از پیاده‌سازی سرور، باید سرویس‌هایی که میخواهیم این سرور بتواند آن‌ها را هندل کند پیاده‌سازی کنیم، از کدهای تولید شده، کلاس RandomExchangerServiceBase به وسیله‌ی کامپایلر ساخته شده، آن‌را extend کرده و توابع آن‌را پیاده‌سازی می‌کنیم.یک نمونه از این پیاده‌سازی در زیر آمده است. در دارت، gRPC از ساختارهای async/await پیش‌فرض دارت استفاده می‌کند، به همین خاطر به سادگی می‌توان کدها را با کمک آن‌ها ساده‌تر پیاده کرد.تابع getSingleRandom با توجه به درخواست فقط یک خروجی بر می‌گرداند.تابع getRandomForEver، یک ثانیه یک بار خروجی بر می‌گرداند و ...import &#039;dart:math&#039;;

import &#039;package:grpc/grpc.dart&#039;;
import &#039;package:flutter_app/generated/random_exchange.pb.dart&#039;;
import &#039;package:flutter_app/generated/random_exchange.pbgrpc.dart&#039;;

class RandomExchangerService extends RandomExchangerServiceBase {
  var randomGenerator = new Random();

  @override
  Future&lt;RandomResponse&gt; getSingleRandom(
      ServiceCall call, RandomRequest request) async {
    return RandomResponse()
      ..value = randomGenerator.nextInt(request.maximum - request.minimum + 1) +
          request.minimum;
  }

  @override
  Stream&lt;RandomResponse&gt; getBidiRandom(
      ServiceCall call, Stream&lt;RandomRequest&gt; requests) async* {
    await for(var request in requests) {
      yield RandomResponse()
        ..value =
            randomGenerator.nextInt(request.maximum - request.minimum + 1) +
                request.minimum;
      await Future.delayed(Duration(milliseconds: 250));
      yield RandomResponse()
        ..value =
            randomGenerator.nextInt(request.maximum - request.minimum + 1) +
                request.minimum;
      await Future.delayed(Duration(milliseconds: 250));
      yield RandomResponse()
        ..value =
            randomGenerator.nextInt(request.maximum - request.minimum + 1) +
                request.minimum;
    }
  }

  @override
  Future&lt;RandomResponse&gt; getSingleRandomWithMultipleConditions(
      ServiceCall call, Stream&lt;RandomRequest&gt; requests) async {
    var minimum = -1000000000;
    var maximum = 1000000000;
    await for (var request in requests) {
      minimum = max(request.minimum, minimum);
      maximum = min(request.maximum, maximum);
    }
    maximum = max(minimum, maximum);
    return RandomResponse()
      ..value = randomGenerator.nextInt(maximum - minimum + 1) + minimum;
  }

  @override
  Stream&lt;RandomResponse&gt; getRandomForEver(
      ServiceCall call, RandomRequest request) async* {
    while (!call.isCanceled &amp;&amp; !call.isTimedOut) {
      yield RandomResponse()
        ..value =
            randomGenerator.nextInt(request.maximum - request.minimum + 1) +
                request.minimum;
      await Future.delayed(Duration(seconds: 1));
    }
  }
}حال نوبت این است که یک سرور روی این Service بالا بیاوریم، grpc خودش امکانات زیادی، از جمله gzip، استفاده از tls و ... را به ما می‌دهد که بهتر است آن‌ها را در محیط پروداکشن فعال کنیم، اما اکنون یک سرور با کانکشن insecure می‌سازیم تا درگیر پیچیدگی‌های ساختن certificate و ... نشویم.کلاس Server از پکیج grpc برای اینکار به ما کمک می‌کند، نمونه کد زیر، روی پورت ۵۰۰۵۱ یک سرور خام که سرویس بالا از سرویس‌هایش هستند بالا می‌آورد:final server = Server(
  [RandomExchangerService()],
);
await server.serve(address: &#039;0.0.0.0&#039;, port: 50051);این کد را داخل main قرار می‌دهیم:Future&lt;void&gt; main(List&lt;String&gt; args) async {
  final server = Server(
    [RandomExchangerService()],
  );
  await server.serve(address: &#039;0.0.0.0&#039;, port: 50051);
  print(&#039;Server listening on port ${server.port}...&#039;);
}کد کامل سرور را در لینک روبرو می‌توانید ببینید: https://github.com/ATofighi/FlutterGRPC-Example/blob/master/lib/server.dartتست کردن سرور با کمک Kreyaچون فرمت grpc به صورت باینری است، بدون یک کلاینتی که grpc بفهمد، نمی‌توان به سادگی آن را دیباگ و تست کرد، برای همین خاطر، تعداد debugger که gui به ما بدهند به وجود آمده که با کمک آن‌ها می‌توان ریکوئست از نوع grpc زد و خروجی را به شکل human readable که برای انسان قابل فهم باشد دید. یکی از این ابزار‌های Kreya است. از طریق لینک روبرو اقدام به نصب آن کنید: https://kreya.app/downloads/پس از نصب و باز کردن آن، با صحنه‌ی زیر روبرو می‌شوید:شروع کار با Kreyaسپس روی Create project بزنید و یک پروژه‌ی جدید بسازید، سپس در Project، در Importers، یک ایمپورتر جدید ساخته، نوع آن‌ را grpc proto files بگذارید و از طریق بخش انتخاب پوشه، پوشه‌ی protos را انتخاب کنید و save کنید و از بالا راست Back بزنید.در صفحه‌ی پیش رو، اطلاعات سرور grpc شامل endpoint و ... را می‌خواهد، certificate آن را disable کنید و endpoint را http://localhost:50051 وارد کنید.سپس از منوی سمت چپ، می‌توانید Service و RPCهای درون آن را پیدا کنید، مثلا getSingleRandom را انتخاب می‌کنیم و یک درخواست به آن می‌فرستیم:ارسال یک درخواست ساده با کمک Kreyaمی‌توانید بقیه‌ی RPCها را هم تست کنید تا مطمئن شویم سرور کار می‌کند.نوشتن کلاینت با فلاترتا اینجا مطمئن شدیم که سرور کار می‌کند، حال لازم است که اپ فلاتر را بنویسیم، در این اپ از سه استیت مربوط به minimum، maximum و value که مقدار تصادفی خروجی آمده از سرور است استفاده می‌کنیم، در بالای صفحه دو فیلد برای وارد کردن کمینه و بیشینه، در وسط صفحه عدد تصادفی تولید شده و در پایین صفحه دکمه‌های اجرای RPCها را قرار می‌دهیم. یک کد ابتدایی که Widgetها را شامل شود کد زیر است:import &#039;package:flutter/cupertino.dart&#039;;
import &#039;package:flutter/material.dart&#039;;

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: &#039;Flutter Demo&#039;,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: &#039;Flutter GRPC Test&#039;),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() =&gt; _MyHomePageState();
}

class _MyHomePageState extends State&lt;MyHomePage&gt; {
  int _value = 0;
  int? _minimum = 0;
  int? _maximum = 10000;
  TextEditingController _minimumFieldController = TextEditingController();
  TextEditingController _maximumFieldController = TextEditingController();

  void _getSingleRandom() async {
    // TODO
  }

  void _getRandomsForEver() async {
    // TODO
  }

  void _getMultiConditionRandom() async {
    // TODO

  }

  void _getBidiRandom() async {
    // TODO
  }

  void _cancelRequest() async {
    // TODO
  }

  @override
  void initState() {
    super.initState();
    // TODO
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: Center(
          child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: &lt;Widget&gt;[
                Text(&amp;quotEnter bounds of range&amp;quot),
                Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    Spacer(),
                    Expanded(
                        child: TextField(
                      d: (value) {
                        setState(() {
                          _minimum = int.tryParse(value);
                        });
                      },
                      controller: _minimumFieldController,
                      decoration: InputDecoration(hintText: &amp;quotmin&amp;quot),
                    )),
                    Spacer(),
                    Expanded(
                        child: TextField(
                      d: (value) {
                        setState(() {
                          _maximum = int.tryParse(value);
                        });
                      },
                      controller: _maximumFieldController,
                      decoration: InputDecoration(hintText: &amp;quotmax&amp;quot),
                    )),
                    Spacer(),
                  ],
                ),
                Spacer(),
                Text(
                  &#039;You random number is:&#039;,
                ),
                Text(
                  &#039;$_value&#039;,
                  style: Theme.of(context).textTheme.headline4,
                ),
                Spacer(),
              ]),
        ),
        floatingActionButton: SingleChildScrollView(
            scrollDirection: Axis.horizontal,
            child: Wrap(
              spacing: 10,
              alignment: WrapAlignment.spaceEvenly,
              children: [
                  OutlinedButton(
                      onPressed: _getSingleRandom,
                      child: Text(&amp;quotGet Single Random&amp;quot)),
                  OutlinedButton(
                      onPressed: _getMultiConditionRandom,
                      child: Text(&amp;quotGet MultiConditional Random&amp;quot)),
                  OutlinedButton(
                      onPressed: _getRandomsForEver,
                      child: Text(&amp;quotGet Multiple Randoms&amp;quot)),
                  OutlinedButton(
                      onPressed: _getBidiRandom,
                      child: Text(&amp;quotGet Bidi Randoms&amp;quot)),
                  OutlinedButton(
                      onPressed: _cancelRequest, child: Text(&amp;quotCancel Request&amp;quot))
              ],
            )));
  }
}با اجرای آن ظاهر زیر را می‌بینیم:ظاهر ابتدایی اپ :) البته این ظاهر با اصول طراحی فاصله دارد، اما برای اینکه کد پایه‌ی این مقاله شلوغ نشود و بتوانیم به اصل مطلب بپردازیم، از آن اغماض می‌کنیم.اتصال به سرور و ساختن یک stubبرای اتصال به سرور به دو آبجکت نیاز داریم:1. channelکه وظیفه‌ی برقرار ارتباط با سرور با کمک هاست و پورت مربوطه را برعهده دارد، همچنین هندل‌کردن certificate و یا گزینه‌های دیگر چون فشرده سازی برعهده‌ی channel است، از پکیج grpc/grpc.dart، کلاس ClientChannel چنین کاری را برایمان انجام می‌دهد، اگر فرض کنیم سرور روی آی‌پی 192.168.43.8 بالا آمده است، کد زیر یک channel می‌سازد:final channel = ClientChannel(&#039;192.168.43.8&#039;,
    port: 50051,
    options:
        const ChannelOptions(credentials: ChannelCredentials.insecure()));2. stubدر کنار channel، نیاز به یک stub داریم، stub، یک پیاده‌سازی از service است که اجرای هر کدام از توابع آن (که توابع سرویس هستند)، با کمک channel، یک rpc به سرور را در پیش دارد. یکی از کلاس‌های کدهای autogenerated مربوط به این پیاده‌سازی است که برای سرویس RandomExchangerService، کلاس RandomExchangerServiceClient است. با کد زیر stub را تعریف می‌کنیم:stub = RandomExchangerServiceClient(channel);یک مکان برای قرار دادن این کد‌ها، تابع initState است، مثلا کد زیر حالت کامل آن است:import &#039;package:grpc/grpc.dart&#039;;

import &#039;generated/random_exchange.pbgrpc.dart&#039;;

.....


late ClientChannel channel;
late RandomExchangerServiceClient stub;

@override
void initState() {
  super.initState();
  channel = ClientChannel(&#039;192.168.43.8&#039;,
      port: 50051,
      options:
          const ChannelOptions(credentials: ChannelCredentials.insecure()));
  stub = RandomExchangerServiceClient(channel);
}


@override
void dispose() {
  super.dispose();
  channel.terminate();
}دقت کنید که در dispose، کانکشن را بستیم، اگر این کار را نکنیم، ممکن است موجب ایجاد memory leak و file descriptor leak در برنامه‌مان شویم.اجرای RPC به صورت Unaryبرای اولین مثال، به پیاده‌سازی تابع _getSingleRandom بپردازیم، این تابع باید با توجه به متغیرهای _minimum و _maximum از State برنامه، یک فراخوان getSingleRandom را اجرا کند و بعد از آنکه خروجی آن آمد، استیت value را بروزرسانی کند.پس باید یک پیام از نوع RandomRequest بسازیم، از کدهای generated، کلاس RandomRequest را داریم که در کانسترکتورش، آرگمان‌های minimum و maximum را می‌گیرد، پس با کد زیر می‌توان این کار را انجام داد:RandomRequest(minimum: _minimum, maximum: _maximum)و می‌توان به stub گفت که درخواست را برایمان بفرستد:stub
        .getSingleRandom(RandomRequest(minimum: _minimum, maximum: _maximum))خروجی از نوع Future&lt;RandomResponse&gt; خواهد بود، برای اینکه پیاده‌سازی راحت شود، می‌توانیم از پترن async await استفاده کنیم، به این صورت که تابع _getSingleRandom را به شکل async پیاده کنیم و روی فراخوان getSingleRandom، بیاییم و await کنیم، در این صورت در ادامه RandomResponse را داریم و به سادگی می‌توانیم استیت را آپدیت کنیم.  void _getSingleRandom() async {
    final response = await stub
        .getSingleRandom(RandomRequest(minimum: _minimum, maximum: _maximum));
    setState(() {
      _value = response.value;
    });
  }به همین سادگی اولین RPCمان را در کلاینت زدیم. :) بقیه‌ی چیزها به لطف پیاده‌سازی grpc انجام می‌شود.پیاده‌سازی RPC به صورت Server Streamingمی‌خواهیم بعد از کلیک روی دکمه‌ی Gen Multiple Random، کلاینت فراخوانی getRandomForEver را صدا کند، سپس تا زمانی که سرور عدد می‌دهد، کلاینت این عدد را نشان دهد، همچنین بعد از کلیک روی دکمه‌ی Cancel، درخواست از طرف کلاینت متوقف شود و دیگر سرور stream نکند.برای اینکار یک state به نام _responseStream اضافه می‌کنیم که استریم را داشته باشیم و اگر خواستیم بتوانیم کنسلش کنیم. خروجی stub.getRandomForEver به صورت Stream&lt;RandomResponse&gt; است، در اینجا هم می‌توان از پترن async/await استفاده کرد و روی stream، فور زد که کد آن به شکل زیر می‌شود:ResponseStream? _responseStream;
void _getRandomsForEver() async {
  final responseStream = stub
      .getRandomForEver(RandomRequest(minimum: _minimum, maximum: _maximum));
  setState(() {
    _responseStream = responseStream;
  });
  await for (var response in responseStream) {
    setState(() {
      _value = response.value;
    });
  }
}

void _cancelRequest() async {
  _responseStream?.cancel();
  setState(() {
    _responseStream = null;
  });
}پیاده‌سازی RPC به صورت Client Streamingدر این حالت فرض کنید که بعد از شروع، یک استیت _streamValues داریم که برابر با true می‌شود، سپس در زمانی که true است، ۱۰ ثانیه  یک‌بار، مقدار minimum و maximum را برای سرور می‌فرستیم، سپس وقتی روی cancel کلیک کردیم، _streamValues برابر با false شود و جریان ارسال تمام شود. باید stub.getSingleRandomWithMultipleConditions را صدا کنیم، این تابع به عنوان ورودی Stream&lt;RandomRequest&gt; می‌گیرد، یک راه برای ساختن Stream، استفاده از روش async/await است، به این صورت که یک تابع از نوع async* بسازیم که عناصر استریمی که باید ساخته شود را yield کند. مثلا تابع زیر یک نمونه از چنین پیاده‌سازی‌ای است:Stream&lt;RandomRequest&gt; requestBounds() async* {
  setState(() {
    _streamValues = true;
  });
  while (_streamValues) {
    await showDialog(
        context: context,
        builder: (context) =&gt; AlertDialog(
                title: Text(&amp;quotEnter bound in next 10 seconds&amp;quot),
                actions: [
                  TextButton(
                      onPressed: () =&gt; {Navigator.pop(context)},
                      child: Text(&#039;OK&#039;)),
                ]));
    await Future.delayed(Duration(seconds: 10));
    yield RandomRequest(minimum: _minimum, maximum: _maximum);
  }
}ابتدا یک دیالوگ نشان می‌دهد که کاربر متوجه شود باید ۱۰ ثانیه دیگر ورودی وارد کند، سپس ۱۰ ثانیه صبر می‌کند و یک RandomRequest را yield می‌کند. البته این مدل پیاده‌سازی خیلی با اصول فاصله دارد، اما برای سادگی و دیدن اصل مطلب، وارد جزئیات طراحی محصول نشدیم.کد کامل آن به شکل زیر می‌شود:  bool _streamValues = false;

  void _getMultiConditionRandom() async {
    Stream&lt;RandomRequest&gt; requestBounds() async* {
      setState(() {
        _streamValues = true;
      });
      while (_streamValues) {
        await showDialog(
            context: context,
            builder: (context) =&gt; AlertDialog(
                    title: Text(&amp;quotEnter bound in next 10 seconds&amp;quot),
                    actions: [
                      TextButton(
                          onPressed: () =&gt; {Navigator.pop(context)},
                          child: Text(&#039;OK&#039;)),
                    ]));
        await Future.delayed(Duration(seconds: 10));
        yield RandomRequest(minimum: _minimum, maximum: _maximum);
      }
    }

    final response =
        await stub.getSingleRandomWithMultipleConditions(requestBounds());
    setState(() {
      _value = response.value;
    });
  }
پیاده‌سازی RPC به صورت Bidirectionalاین روش ترکیبی از دو روش قبلی می‌شود، باید ورودی یک Stream&lt;RandomRequest&gt; دهیم و خروجی هم یک Stream&lt;RandomRequest&gt; است، ترتیب ورودی دادن و خروجی دادن مهم نیست، می‌توانیم هر تعداد ورودی بدهیم و هر تعداد خروجی به هر ترتیبی براساس لاجیک برنامه داشته باشیم، مثلا در این کلاینت و سرور، یک درخواست از کلاینت و سپس سه درخواست از کلاینت و دوباره به همین ترتیب انجام می‌شود، توضیحات کلی انجام این کار داده شده و فقط به پیاده‌سازی آن اشاره می‌کنیم:  void _getBidiRandom() async {
    Stream&lt;RandomRequest&gt; requestBounds() async* {
      while (_streamValues) {
        await Future.delayed(Duration(seconds: 1));
        yield RandomRequest(minimum: _minimum, maximum: _maximum);
      }
    }

    setState(() {
      _streamValues = true;
    });

    final responseStream = stub.getBidiRandom(requestBounds());
    setState(() {
      _responseStream = responseStream;
    });

    await for (var response in responseStream) {
      setState(() {
        _value = response.value;
      });
    }
  }پس این روش هم انجام شد.کد کامل این پیاده‌سازی را از اینجا می‌توانید ببینید: https://github.com/ATofighi/FlutterGRPC-Example/blob/master/lib/main.dart در ادامهتا اینجا یک پیاده‌سازی اولیه از چهار روش ارتباطی در gRPC را دیدیم، اما این هنوز ابتدای راه است، کارهای دیگری هم لازم است برای اینکه محصول ما ارزش‌آفرینی داشته باشد انجام دهیم که در این مطلب به جزئیات آن‌ها نمی‌پردازیم، از جمله این موارد، موارد زیر هستند:هندل کردن خطا‌ها و اکسپشن‌هادر لحظه‌های await، ممکن است به هر دلیل خطا بخوریم و RandomRequest بر نگردد، مثلا ارتباط با سرور قطع شود، درخواست timeout شود و یا خودمان با فشاردادن cancel درخواست را timeout کنیم. سرور خطا بخورد و ...، بهتر است انواع این خطاها را catch کنیم و به کاربر خطای مناسب را نشان دهیم.استفاده از metadataهایکی از موارد در gRPC که به آن نپرداختیم، metadataهاست، متادیتاها مشابه آن‌چه از Headerها در http داشتیم عمل می‌کنند، عموما داده‌هایی مثل authorization، timeoutها و دیتاهایی که در تمام درخواست‌ها باید ارسال شوند را در metadata می‌گذاریم. برای این‌کار، پارامتر دوم توابع stub، آرگمان نام‌دار options است که کلاس CallOptions را ورودی می‌گیرد و این کلاس، آرگمان metadata دارد، مثلا کد زیر یک نمونه از آن است:stub.getSingleRandom(request, options: CallOptions(timeout: Duration(milliseconds: 100), metadata: {
  &#039;authorization&#039;: &#039;xx&#039;,
  &#039;a&#039;: &#039;b&#039;,
}));جی‌آر‌پی‌سی بر روی مرورگربا اینکه فلاتر cross platform است، اما پروتوکل gRPC با توجه به ماهیتش هم‌اکنون روی مروگر‌ها پشتیبانی نمی‌شود، پس منطقا کدهایی که زدیم هم روی مرورگرها پشتیبانی نمی‌شوند! برای حل این مشکل، پروتوکل دیگری به نام gRPC-web پیاده‌سازی شده که gRPC را روی پروتوکل‌های HTTP به صورتی که مروگرها آن را پشتیبانی کنند سوار شده است، برای استفاده از آن باید یا از سروری که grpc-web را پشتیبانی کند استفاده کنیم و یا اینکه یک proxy که grpc را به grpc-web تبدیل کند (مثل envoy proxy) در سمت سرور استفاده کنیم. در سمت کلاینت هم از کانال GrpcWebClientChannel استفاده کنیم، در stubها تغییر خاصی اتفاق نمی‌افتد، اما باید دقت کنیم در برخی از حالت‌ها حالت bidirectional دیگر پشتیبانی نمی‌شود.منابعکد پیشفرض android studio برای یک application از نوع flutterhttps://github.com/grpc/grpc-dart/tree/6c16fceb2a1d6c153e2432d42f21553dcf500766/examplehttps://flutter.dev/docs/cookbook/networking/web-socketshttps://grpc.io/docs/what-is-grpc/core-concepts/https://grpc.io/docs/what-is-grpc/introduction/https://grpc.io/docs/languages/dart/quickstart/نویسندگان:ارشیا مقیمیحامد علی‌محمدزادهعلیرضا توفیقی محمدیتهیه شده برای درس برنامه‌نویسی موبایل ارائه شده در نیم‌سال دوم تحصیلی ۰۰-۹۹ دانشگاه صنعتی شریف</description>
                <category>علیرضا توفیقی محمدی</category>
                <author>علیرضا توفیقی محمدی</author>
                <pubDate>Tue, 13 Jul 2021 03:04:40 +0430</pubDate>
            </item>
                    <item>
                <title>پیر پروگرمینگ در محصولات JetBrains</title>
                <link>https://virgool.io/wptips/jetbrains-pair-programming-pzmbvpvg8p1l</link>
                <description>کاور پلاگین Code With Me، محصول شرکت JetBrainsسلام.حدود ۸ ماهی هست که Jetbrains، پلاگین Code with Me رو معرفی کرده که می‌شه باهاش مثل LiveShare توی VSCode یک محیط برنامه‌نویسی رو توی محصولات Jetbrains مثل IDEA، Pycharm, phpStorm و ... رو بین چند نفر به اشتراک گذاشت و تجربه‌ی برنامه‌نویسیِ باهم رو به شکل دورکاری داشت. توی این پست می‌خوام این محصول و نحوه‌ی استفاده ازش و چندتا تکنیک دیگه برای اینکه بتونیم تجربه‌ی خوبی از Pair programming داشته باشیم باهاتون به اشتراک بگذارم.نیازمندی‌هااین ویژگی از نسخه‌ی 2020.2.1 به بعد محصولات Jetbrains اضافه شده، پس لازمه که کسی که می‌خواد میزبان بشه، نسخه‌ی IDEش حداقل 2020.2.1 باشه، اینو بگم که لازم نیست ورژن professional باشه و روی نسخه‌های community هم این فیچر وجود داره.دقت کنید تقریبا هرچی که می‌گم برای کسی هست که میزبان می‌شه، کسایی که میخوان وصل بشن و میهمان هستن لازم نیست این کارا رو بکنن تا برسیم به بخش میهمان‌ها :دینصب پلاگین Code With Meتوی IDEتون برید توی File بعد توی Settings، بعد توی قسمت Plugins، و در قسمت جستجوش، جستجو کنید Code With Me، پلاگین رو پیدا کنید و روی Install کلیک کنید.صفحه‌ی نصب، تب Plugins و جایی که می‌شه سرچ کرد رو با فلش مشخص کردم، به آیکُن Code with me دقت کنید، اگر نسخه‌تون قدیمی باشه پیداش نمی‌کنید. نویسنده‌ش هم خود JetBrains هستش که توی شکل مشخص کردم.نکته: اگر کلاً قسمت plugins کار نکرد، احتمالا برای اینکه آپدیت شدن jetbrains رو دور بزنید قبلا توی /etc/hosts  احتمالا کلا jetbrains رو کاری کردین که یک جای نامعلومی رو ریسالو کنه، برید درستش کنید تا بشه نصب کرد پلاگینو :))بعد از نصب احتمالاً ازتون می‌خواد که IDEتون رو ری‌استارت کنید. این کارو بکنید.استفاده از Code with Me برای میزبانبعد از نصب یک آیکُن برای Code with me به بالا راست صفحه اضافه می‌شه که با اون می‌تونید یک جلسه‌ی جدید کد زدن باهم رو شروع کنید.با کلیک روی این دکمه، چندتا گزینه داره که می‌تونید یک جلسه‌ی جدید شروع کنید، جلسه رو متوقف کنید و دسترسی‌هایی که آدما دارن رو عوض کنید  و ...متأسفانه سرور پیش‌فرض Code with me رو نتونستم توی ایران استفاده کنم؛ اما نکته‌ی مثبت و خوش‌حال کننده این است که ورژن on-promise داره و من روی وی‌امم یه دونه‌شو بالا آوردم که شما هم می‌تونید ازش استفاده کنید.برای این کار از قسمت Files، برید توی Settings؛ بعد برید توی قسمت Code with Me، گزینه‌ی Lobby server URL رو مقدارشو بهhttps://jetbrains-lobby.atofighi.irبگذارید. بعدش که ذخیره کنید و از دکمه‌ی Code with me که بالا راسته سشن جدید بسازید، برای اینکار روی دکمه‌ی Code with me کلیک کنید، بعد Enable Access and Copy Invitation Link کلیک کنید.توی صفحه‌ی جدید می‌تونید میزان دسترسی آدمایی که به عنوان میهمان اضافه می‌شن رو مشخص کنید، مثلا کلا بهشون دسترسی بدین، فقط دسترسی نوشتن یا خوندن بدین یا اینکه مثلا دسترسی یه سری فایل خاص رو از طریق تب Custom بگیرید. بعدش روی Enable Access کلیک کنید. بعدش یک لینک بهتون داده می‌شه که توی Clipboard هست، لینک شبیه لینکی مثل این است:https://jetbrains-lobby.atofighi.ir/felaan#p=PY&amp;fp=bahmaanاین لینک رو به دوستتون بدید تا به عنوان مهمان وارد بشه، دقت کنید که بعد از اینکه دوستتون تلاش کرد وارد بشه از شما سوال می‌شه که بهش دسترسی بدین یا ندین. (جلسات حداکثر ۵ نفره می‌تونن باشن.)اکسپت رو بزنید تا دوستتون اضافه بشه.پلاگین Code with Me برای میهمانبه عنوان میهمان لازم هست که یک کلاینت خیلی سبک که محیطی شبیه به IDEهای اصلی داره باید نصب کنید، کافی است لینکی که میزبان بهتون داده رو باز کنید.اگر کلاینت رو از قبل نصب داشته باشید برنامه‌ی IntellJ Client باز می‌شه و منتظر تأیید میزبان می‌شه.در غیر این صورت روی Something going wrong توی صفحه‌ای که براتون اومده کلیک کنید و آموزش نصب کلاینت رو دنبال کنید.برای ویندوز باید یک برنامه نصب کنید و برای لینوکس یک دستور رو توی terminal باید اجرا کنید.خب، بعد از اجرا تقریبا کار تمومه.یک نمونه از اجرا.خب از Pair programming استفاده کنید. نکات و تجربه‌هایی که باعث می‌شه تجربه‌تون بهتر بشه رو در ادامه میگم.نکات اضافهفالو کردن و فالو شدنکنار دکمه‌ی Code with Me در بالا راست، لیست افرادی که توی جلسه هستن رو می‌بینید، اگه روی یه نفر کلیک کنید می‌تونید فالوش کنید، ببینید کجاست و scroll شدن و ایناشون رو هم می‌بینید.اشتراک ترمینالمی‌تونید ترمینال هم به اشتراک بگذارید، یه مشکلی که من دیدم این بود که وقتی میزبان ترمینال جدید می‌ساخت به اشتراک گذاشته نمی‌شد، ولی میهمان‌ها ترمینال‌هایی که میساختن مشترک بود. فقط دقت کنید که دسترسی ترمینال خیلی دسترسیِ زیادیه، مثلا می‌تونن میهمان‌ها به کل داده‌های کامپیوترتون دسترسی داشته باشن که اگه بهشون اعتماد ندارید ریسک داره :دیویس و ویدئوبه نظر توی نسخه‌های جدید‌تر code with me امکان اشتراک ویس و ویدئو با jitsi هم وجود داره، ولی من تست کردم نبود :-( (اگه فهمیدید چجوری باید اضافه بشه به منم یاد بدین :دی) از google meet و اینا می‌شه استفاده کرد و صدا رو هم. اشتراک سرور!یکی از چیزایی که خیلی لازم می‌شه مخصوصا وقتی وب پیاده می‌کنیم اینه که مثلاً پورت ۸۰۰۰ رو به اشتراک بگذاریم بین خودمون.برای اینکار ابزار‌هایی وجود داره که پورت به اشتراک بگذاریم، ابزار مطمئن و راحتی که پیدا کردم https://localhost.run هست، مثلا اگه پورت ۸۰۰۰ رو بخوایم به اشتراک بگذاریم کافی هست این دستور رو توی ترمینال بزنیم:ssh -R 80:localhost:8000 localhost.runدر جواب بهتون یک url می‌ده که اون رو به اشتراک بگذارید اوکیه. امنیتش هم در اندازه‌ی port forwarding اس‌اس‌اچ هستش و خب دسترسی عجیبی به کامپیوترتون پیدا نمی‌کنه.امنیتدقت کنید که کدهاتون توی سرور قرار نمی‌گیره و ارتباط به صورت end-to-end encryption خواهد بود، به همین خاطر نگرانی دزدی شدن کدتون رو نباید داشته باشید.راه حل‌های آلترناتیوجز Code with me ابزارهای دیگه‌ای هم هستن که روی jetbrains کار می‌کنن، یکی از نمونه‌هاشون https://www.codetogether.com هست، این لینک یک مقایسه بینشون انجام داده (البته فکر کنم کمی دقیق نیست مقایسه‌ش) و می‌تونید انتخاب کنید.در آخرخب، حرفام تموم شد. کلاً که از اهمیت Pair programming و مزایاش هرچی بگیم کمه و دوران دورکاری باهم کد زدن خیلی کم شده، اگه تجربه‌ی خوبی داشتید یا ابزاری می‌شناسید که تجربه‌ی خوبی ازش داشتید لطفا توی دیدگاه‌ها به منم معرفی کنید. فازو داشتید دوست دارم در آینده در بلاگم هم ازین مدل مطالب بذارم (ان‌شاءالله) و دنبالم کنید :دیدمتون گرم.</description>
                <category>علیرضا توفیقی محمدی</category>
                <author>علیرضا توفیقی محمدی</author>
                <pubDate>Fri, 29 Jan 2021 16:16:51 +0330</pubDate>
            </item>
                    <item>
                <title>ناهمواری‌ها در نقشه و مسیریاب بلد</title>
                <link>https://virgool.io/BaladMaps/hillshade-ie5oc0upmmeb</link>
                <description>به نام خدادر این مطلب، به همراه همکار خوبم عرفان قادرمرزی، می‌خواهیم به صورت ساده، در مورد اطلاعات ناهمواری در بلد صحبت کنیم، اینکه این اطلاعات چه هستند و در کجا و چگونه در اپلیکیشن بلد استفاده می‌شوند صحبت کوتاهی داشته‌باشیم.بلد به عنوان یک پلتفرم نقشه سعی می‌کند تا انواع نیازهای مکان محور کاربران را رفع کند، برای رفع این نیاز‌ها در بلد از انواع داده‌های مکان‌محور استفاده می‌کنیم، داده‌های معابر، داده‌های کسب‌و‌کارها که با کمک کاربران بلد هم بهبود داده می‌شود، تصاویر ماهواره‌ای و ... . اطلاعات ناهمواری‌ها نیز جزئی از داده‌هایی هستند که بلد با کمک آن‌ها سعی در ارائه‌ٔ خدمات بهتری برای کاربران می‌کند.در ادامه ابتدا توضیح می‌دهیم که داده‌ٔ ناهمواری‌ها به چه صورت است، سپس در مورد اینکه چگونه از آن برای نمایش دادن کوه‌ها در بلد استفاده می‌شود به عنوان یک مثال توضیح می‌دهیم.مدل رقومی ارتفاع (DEM)به نقل از ویکی‌پدیای فارسی:مدل رقومی ارتفاع (انگلیسی: Digital elevation model) یا DEM مدلی دیجیتال یا نمایشی سه‌بعدی از سطح زمین، ماه یا دیگر سیاره‌ها است که معمولاً برای نمایش ناهمواری‌های زمین و با استفاده از داده‌های ارتفاع از سطح دریا تهیه می‌شود.دیتای خام ما مدل رقومی ارتفاع است که همان‌طور که ویکی‌پدیا توضیح داده‌است، ارتفاع از سطح دریای هر نقطه از ایران را در آن داریم. دیتای مدل رقومی را می‌توان در قالب یک مستطیل n در m دید که هر خانهٔ آن معرف یک ناحیه از زمین است و مقدار آن نشان‌دهنده‌ٔ ارتفاع از سطح دریای آن نقطه است.تصویر۱. نمونه‌ای از «مدل رقومی ارتفاع»، در این داده هر نقطه نمایانگر ارتفاع یک محدوده در ایران است، نقاط روشن‌تر ارتفاع‌های بیشتر و نقاط تیره‌تر ارتفاع‌های نزدیک به سطح دریا را نشان می‌دهد، فرم رشته کوه‌ها کاملا مشخص است. همچنین نقطه‌ای که با پیکان قرمز مشخص شده ‌است قلهٔ دماوند است.همان‌طور که در تصویر هم مشاهده می‌کنید با اینکه مدل رقومی ارتفاع، ناهمواری‌ها را به خوبی نشان می‌دهد، ولی در ذهن تصویر سه‌بعدی از کوه‌ها را به وجود نمی‌آورد، پس لازم است پردازشی روی مدل رقومی ارتفاع انجام بدیم تا به تصویری سه بعدی (یا شبه سه بعدی) از ناهمواری‌ها و کوه‌ها برسیم.تپه‌سایه (Hillshade)اصولا همهٔ نقشه‌ها بخشی از واقعیت موجود در زمین را با تغییراتی نشان می‌دهند. اگر بخواهیم زمین را مانند آن‌چه که هست نشان بدهیم (مثل عکس‌های ماهواره‌ای) نتیجه ممکن است قشنگ باشد ولی کاربر ممکن است نتواند اطلاعاتی که لازم دارد را به راحتی پیدا کند. بنابراین همیشه در تهیهٔ نقشه‌ها یک سری ساده‌سازی‌ها انجام می‌شود و فقط اطلاعات لازم نمایش داده می‌شود. از طرف دیگر ساده‌کردن بیش از حد هم نقشه را خسته‌کننده می‌کند. هنر طراحی نقشه یا کارتوگرافی در این است که بتواند بین بخش‌های کاربردی نقشه و زیبایی آن تعادل ایجاد کند. استفاده از رنگ و سایه‌روشن و تغییر مقیاس برخی چیزها در نقشه کمک می‌کند که نقشه در عین کاربردی بودن از لحاظ بصری هم جذابیت داشته باشد.از آن‌جا که زمین یک شئ سه‌بعدی است و نقشه یک نگارهٔ دو بعدی است به ناچار برخی اطلاعات زمین مثل ارتفاع نقاط از دست می‌رود و باید برای نمایش آن فکر دیگری کرد. تکنیک‌های مختلفی برای نمایش پستی و بلندی‌های زمین وجود دارد. مثلا استفاده از خطوط هم‌ارتفاع (کانتور contour) یک راه فنی مناسب برای درک ارتفاعات روی نقشه است. این تکنیک البته بیشتر در نقشه‌های فنی و عمرانی کاربرد دارد و برای نقشه‌های با کاربرد عام از روش‌های دیگری مثل هیل‌شید (تپه‌سایه) استفاده می‌شود.تپه‌سایه چیست؟از لحاظ تئوری اگر شما عمود بر یک صفحه به آن نگاه کنید تغییرات ارتفاعی در آن را متوجه نمی‌شوید. (مثلا اگر به یک استوانه از کنار نگاه کنیم مستطیل دیده می‌شود و در ظاهر فرقی با یک مکعب ندارد). ولی اگر به این جسم نوری تابانده شود بر سطح آن سایه‌هایی ایجاد می‌شود که به چشم کمک می‌کند که عمق را در آن تشخیص دهد. این قضیه برای سطح زمین هم همین‌طور است و مثلا هنگامی که از هواپیما به کوه‌ها وتپه‌ها نگاه می‌کنیم با تابش نور خورشید به آن‌ها و دیدن سایه روشن‌ها کلیتی از حجم و فرم آن‌ها را درک می‌کنیم. حالا اگر از همین تکنیک روی نقشه استفاده کنیم و یک نقطه را به عنوان محل فرضی خورشید در نظر بگیریم و نقاطی که رو به این منبع نوری هستند روشن‌تر و نقاط پشت به آن را تیره‌تر نشان بدهیم می‌توانیم تا حد زیادی حس دیدن ناهمواری‌ها را در کاربر ایجاد کنیم. این کار اوایل دستی انجام می‌شد و کارتوگرافرهای زبردستی مثل ادوارد ایمهوف (Eduard Imhof) با تکیه بر تجربه و ذوقشان به نقشه عمق می‌دادند و حس واقع‌گرایی را در آن ایجاد می‌کردند.تصویر ۲. سمت راست آقای ادوارد ایمهوف در حال ترسیم دستی تپه‌سایه‌ها، سمت چپ یکی از نقشه‌های ترسیمی او که ناهمواری‌ها را به زیبایی نشان داده. (منبع در پ.ن. ۱) اما در عصر نقشه‌های دیجیتال تپه‌سایه‌ها چطور تهیه می‌شوند؟در بالا گفتیم که اطلاعات پستی و بلندی‌های زمین در یک فایل DEM ذخیره می‌شوند که در آن هر پیکسل از فایل نماینده ناحیه‌ای از زمین است (بسته به رزولوشن فایل DEM اندازه این ناحیه متفاوت است) و رنگ هر پیکسل نشان‌دهندهٔ ارتفاع آن از سطح دریاست. برای تهیهٔ سایه‌روشن‌ها فرض می‌شود که نور خورشید از بالا سمت چپ و با ارتفاع مشخصی به این سطح می‌تابد. حالا چرا بالا و چپ؟ این مساله ربطی به موقعیت واقعی خورشید نسبت به زمین ندارد و به ادراک بصری انسان بستگی دارد. در زندگی روزمره ما عموما عادت داریم اشیاء را طوری ببینیم که نور از بالا به آن تابیده می‌شود.تصویر ۳. کدام‌یک تپه و کدام‌یک گودال هستند؟در این شکل تصاویر فقط ۱۸۰ درجه چرخیده‌اند. ولی ما یکی را تپه می‌بینیم و یکی را گودال. به خاطر این عادت چشم انسان، ما موقعیت خورشید را همیشه از بالا در نظرم می‌گیریم که اتفاقا در نیم‌کره‌ٔ شمالی خلاف موقعیت واقعی خورشید در آسمان است.خب پس از مشخص شدن منبع نوری، تعیین اینکه یک نقطه در معرض نور است یا در سایه کار ساده‌ای است. کافی است از این نقطه به منبع نوری وصل کنیم و اگر در مسیرش به نقطهٔ دیگری برخورد کرد یعنی در سایه آن قرار گرفته ولی اگر بدون مانع به منبع نوری وصل شد یعنی در روشنایی است. البته این «ساده» که گفتیم یعنی مفهوم و روند آن ساده است و از لحاظ فنی مسائل زیادی وجود دارد که باید حل شود. مثلا اغراق در ارتفاعات برای نشان دادن واضح‌تر سایه‌ها لازم است. احتمالا خبر دارید که زمین از لحاظ ناهمواری نسبی (یعنی نسبت ارتفاع دره و کوه‌ها به شعاع) از یک پرتقال هموارتر است! به همین خاطر سایه‌هایی که ایجاد می‌شود اگر با مقیاس واقعی ترسیم شوند خیلی کوچک می‌شوند و عملا دیده نمی‌شوند.در نهایت پس از استخراج تپه‌سایه‌ها ما یک فایل رستر داریم که هنوز برای نمایش روی نقشه بلد مناسب نیست. چون هم حجم زیادی دارد هم اینکه نمی‌توان به سادگی آن را با بقیه عوارض نقشه ترکیب کرد.تصویر ۴. نسخه‌ٔ اولیه از تپه‌سایه، در تصویر بالا خورشید از گوشه‌ی بالا چپ با زاویه‌ی ۴۵ درجه نسبت به محور z می‌تابد، نقاط نورانی نقاطی هستند که نور خورشید به آن‌ها می‌رسد و نقاط تیره نقاطی هستند که نور خورشید به آن‌ها نمی‌رسد، ناحیه‌های مسطح نور خورشید به اندازهٔ متوسط به آن‌ها برخورد می‌کند و نه کاملا روشن و نه کاملا تیره‌اند. دوباره قله‌ٔ دماوند با فلش مشخص شده است.تا الان به چیزی رسیدیم که احساس ناهمواری بودن و کوه بودن به آن داریم، اما همین تصویر چندین گیگابایت حجم دارد و نمی‌توان آن‌را به همین شکل ارائه داد.در ادامه توضیحات مختصری در مورد نمایش تپه‌سایه بر روی گوشی می‌پردازیم.وارد کردن تپه‌سایه در اپلیکیشنیکی از دغدغه‌های ما در بلد حجم دیتای مصرفی کاربرانمان است، ما سعی می‌کنیم بیشترین داده‌ای که برای کاربران مفید و جذاب است را با کمترین مصرف دیتای کاربر ارائه کنیم، پس این موضوع مهم است که تپه‌سایه را به گونه‌ای ارائه کنیم که دیتای مصرفی کاربر زیاد نشود.کاشی‌هااولین کار ما تبدیل تپه سایه به کاشی‌ (Tile)‌هایی‌ست که فقط آن‌ها نمایش داده شوند، وقتی در بلد در زوم کمتر یعنی در حالتی که همه‌ی ایران مشخص است، به ایران نگاه می‌کنید، کافی‌ست اطلاعات در حد ایران دانلود شوند و لازم نیست اطلاعات جزئی‌تر مانند اطلاعات مربوط به پلاک خانه‌ها و ... دانلود شوند، در مورد تپه‌سایه هم همین طور، وقتی تصویر در سطح کل ایران را می‌بینیم لازم نیست تپه‌سایه‌ای به دقت زیاد مشاهده‌کنیم، کافی‌ست دقت در سطح ایران باشد، همچنین وقتی به جلو می‌رویم، باید اطلاعات دقیق‌تری مشاهده کنیم. همچنین هنگامی که نقشه در سطح تهران را مشاهده می‌کنیم لازم نیست اطلاعات شهر دیگری مثل یزد دانلود شود. پس داده‌ها رو به دو صورت تقسیم می‌کنیم، اولا برای هر مقیاس از نقشه و میزان جلو رفتن داده‌هایی برای آن دقت می‌سازیم و سپس داده‌ها را بین ناحیه‌های مختلف تقسیم می‌کنیم تا وقتی شما بخشی از تهران را می‌بینید فقط داده‌های آن بخش از تهران دانلود شوند.تصویر ۵. نمایش کاشی‌ها، هر کدام از مربع‌های قرمز یک کاشی هستند که داده‌های نقشه در آن ذخیره شده‌اند، وقتی نمای کلی ایران را می‌بینیم کاشی‌ها مساحت بزرگی را شامل می‌شوند، ولی شامل اطلاعات کلی هستند، ولی به سطح شهر می‌رسیم، کاشی‌ها شامل اطلاعات مساحت کمتری از نقشه‌اند ولی اطلاعات با جزئیات بیشتری را داخل خود ذخیره می‌کنند.استخراج چندضلعی‌هاهمچنین تصاویر حجم زیادی دارند، به همین دلیل داده‌ها را تبدیل به شکل‌های هندسی مثل خط و چندضلعی می‌کنیم تا علاوه بر کیفیت و زیبایی، حجم کمتری را به صورت میانگین دانلود کنند، برای اینکار اولا ناحیه‌ی مسطح را از تپه‌سایه حذف کردیم و سپس رنگ‌های نورانی و تاریک را به چندین قسمت تقسیم کرده و برای هر کدام چند‌ضلعی‌هایی ساخیتم، برای اینکه بهتر متوجه شوید مثالی ساده‌شده از روند ساختن این چند ضلعی‌ها را برای ناحیهٔ کاملا تیره ارائه خواهیم کرد.تصویر تپه‌سایه را کاملا مشابه تصویر مدل رقومی ارتفاع می‌توانید به صورت یک مستطیل n در m در نظر بگیرید که در هر خانه از این مستطیل میزان تیرگی آن پیکسل نوشته شده‌است. تمام پیکسل‌هایی که تیرگی آن‌ها از حدی بیشتر است را در نظر بگیرید، این پیکسل‌ها تعدادی مولفه‌ٔ همبندی‌ تشکیل می‌دهند. کوچکترین چندضلعی‌ای که همه‌ی این پیکسل‌ها را شامل می‌شود رسم کرده و با ساده‌سازی آن، به یک چندضلعی نهایی می‌رسیم.تصویر ۶. مراحل به دست آوردن چندضلعی‌ها به صورت ساده برای یک سطح از تیرگی  ۰: تصویر تپه‌سایه در حالت زوم، پیکسل‌-پیکسل بودن آن را می‌توان دید.  ۱: تمام پیکسل‌هایی که تیرگی آن از حدی بیشتر است را قرمز کرده‌ایم.  ۲: کوچکترین چندضلعی‌ای که این پیکسل‌ها در آن قرار بگیرد را رسم کردیم.  ۳: با ساده‌سازی این چندضلعی‌ها به چندضلعی‌های نهایی رسیدیم. سپس با کنار هم قرار دادن تمام این چندضلعی‌ها در سطح‌های تیرگی متفاوت دوباره تصویری مانند تصویر تپه‌سایه‌ی قبل ترسیم خواهد شد.تصویر ۷. دو تصویر بالا چندضلعی‌هایی هستند که محدوده‌هایی که شدت نور در آن‌ها یک مقدار مشخص است را نشان می‌دهند، تصویر بالا راست محدوده‌ها با شدت نور زیاد و روشن است و تصویر بالا چپ محدوده‌ها با شدت نور کم و تاریک است. این محدوده‌ها به شکل اشکال هندسی مثل چند ضلعی در آورده شده‌اند و با کنارهم قرار گرفتن آن‌ها تصویر پایین ساخته شده است.سایر مسائل برای وارد‌کردن تپه‌سایهساختن این چندضلعی‌ها از تصویر نکات جالب و زیادی دارد، این نکات از اینکه چگونه از تصویر به چندضلعی برسیم، چگونه این چند ضعلی‌ها را داخل کاشی‌ها ذخیره کنیم تا جزئیات مربوط به اینکه چگونه چندضعلی‌هایی که می‌سازیم حجم زیادی از دانلود اینترنت کاربرانمان را نگیرند و همچنین زیبایی داشته‌باشند است، حتی اجرای الگوریتم‌های مشهور روی این حجم از داده چندین ماه طول می‌کشد و لازم بود دنبال روش‌های بهینه‌تر با قابلیت توزیع پذیری برای محاسباتمان بگردیم. برای نمونه در مورد یکی از این جزئیات، همان‌طور که در بند قبل گفتیم، کاشی‌ها در مقایس‌های مختلف باید شامل جزئیات متفاوتی باشند تا تپه‌سایه در سطح ایران هم دیده شود ولی حجم مصرفی زیاد نباشد، همچنین در سطح شهر باید جزئیات تپه‌سایه بیشتر شود، مثلا برای یک کوه در سطح ایران نشان دادن دو عدد چند‌ضلعی یکی برای ناحیه‌ٔ روشن و دیگری برای ناحیه‌ٔ تیره کافی است، ولی وقتی زوم می‌کنیم باید جزئیات بیشتری از کوه نمایش داده شود. برای این‌کار دو روش داشتیم، یکی این بود که از الگوریتم‌های ساده‌سازی چندضلعی‌ها استفاده کنیم و سعی کنیم با کمک آن‌ها چندضلعی‌های کوچک و زیادی که برای جزئیات زیاد ساخته شده بود را تبدیل به چندضلعی‌هایی برای جزئیات کم کنیم و دیگری این بود که کیفیت تصویر را با روش‌هایی کم کنیم و تصویر با کیفیت کمتر این چندضلعی‌ها را تولید کنیم، الگوریتم‌های ساده‌سازی چندضلعی‌ها اکثر تلاش بر کم‌کردن تعداد اضلاع می‌کنند و کم کردن کیفیت باعث می‌شد که شکل چندضلعی‌ها از حالت عادی خودشان خارج شوند، با ترکیب این دو روش و چند روش دیگر در پردازش تصویر این مسئله نیز حل شد.تصویر ۸. سه مرحله از جزئیات کوه دماوند و ناهمواری‌های اطراف آن را مشاهده می‌کنید، تصویر سمت چپ کوه دماوند فقط از ۵ چندضلعی ساخته‌شده و برای نمایش وقتی داریم کل ایران را مشاهده می‌کنیم مناسب است، تصویر دوم جزئیات بیشتر شده و تصویر از کنارهم قرارگرفتن چندصد چندضلعی تشکیل شده و برای حالتی که با زوم بیشتر نسبت به ایران به کوه‌ها نگاه می‌کنیم مناسب است و تصویر سوم جزئیات بازهم بیشتر شده و برای حالتی که روی کوه دماوند زوم کردیم مناسب است.البته این روش انجام دارای مشکلاتی نیز است، مثلا همان‌طور که در بندهای بالا گفته شد وقتی نقشه را ۱۸۰ درجه بچرخانیم، دیگر تپه‌سایه‌ای که دیده می‌شود به حالت واقعی نزدیک نیست و احساس پستی و بلندی‌ها به بلندی‌ها و پستی‌ها تغییر می‌کند یا با اینکه حجم به طور میانگین از حالت تصویر کمتر است، در نقاطی که جزئیات ناهمواری‌ها زیاد است حجم از حالتی که کاشی‌ها را به صورت تصویر بودند بیشتر می‌شود و یا اینکه بخشی از پردازش و رندر چندضلعی‌ها را برعهده‌ٔ کلاینت گذاشته می‌شود و ....تا حالا فکر می‌کردید نمایش دادن کوه‌ها در نقشه می‌تواند اینقدر جزئیات داشته‌باشد؟ خوب، باهم یکی از استفاده‌های داده‌های ارتفاعی در بلد را دیدیم، این تنها استفاده از این داده‌ها نیست و کاربرد‌های زیادی دارند، مثلا در مسیریابی پیاده شیب مسیر با کمک مدل رقومی ارتفاع محاسبه می‌شود و در تخمین سختی مسیر و زمان رسیدن موثر است.بلد را می‌توانید از کافه بازار و گوگل پلی نصب کنید و صفحهٔ اینستاگرام و توئیتر بلد را از اینجا دنبال کنید.پانویس:۱. منبع تصویر ۳: -  https://hls-dhs-dss.ch/de/articles/031188/2008-01-22/ -  Räber, Stefan &amp; Jenny, Bernhard &amp; Hurni, Lorenz. (2009).</description>
                <category>علیرضا توفیقی محمدی</category>
                <author>علیرضا توفیقی محمدی</author>
                <pubDate>Wed, 29 Jul 2020 18:44:06 +0430</pubDate>
            </item>
                    <item>
                <title>تابستان خود را چگونه گذراندیم؟</title>
                <link>https://virgool.io/hamband/%D8%AA%D8%A7%D8%A8%D8%B3%D8%AA%D8%A7%D9%86-%D8%AE%D9%88%D8%AF-%D8%B1%D8%A7-%DA%86%DA%AF%D9%88%D9%86%D9%87-%DA%AF%D8%B0%D8%B1%D8%A7%D9%86%D8%AF%DB%8C%D9%85-krjzyphz8zoh</link>
                <description>در این پست، آنچه بر همبند در تابستان ۹۸ گذشت آمده است، از شروع کار انجمن علمی تا پایان تابستان.انتخاباتانتخابات این دوره همبند، به دلیل الکترونیکی بودن، یکی از انتخابات پر حرف و حدیث دوره‌های همبند بود. پس از اینکه از علاقه‌مندان به شرکت در انتخابات درخواست شد که در سایت ثبت‌نام کنند، ۱۲۵ نفر ثبت‌نام را انجام دادند و در نهایت انتخابات با شرکت ۱۰۵ نفر از اعضای دانشکده صورت گرفت. به‌این ترتیب اعضای شورای مرکزی همبند مشخص شدند:علیرضا توفیقی محمدی (۸۸ درصد آرا)علی الماسی (۷۶ درصد آرا)احمد رحیمی (۶۴ درصد آرا)محمد شاه‌وردی کندری (۶۰ درصد آرا)پرهام پورمحمدی (۶۰ درصد آرا)جلساتبعد از اتمام امتحانات پایان‌ترم همبند جدید جلساتش را آغاز کرد. اولین جلسه با حضور برخی اعضای دوره‌ی سابق همبند (امیرکسری جلال‌دوست، نیما بهرنگ و پویان علی‌پناهی) به‌منظور انتقال تجربه برگزار شد. در جلسه بعدی، درباره‌ی دبیر و  اعضای علی‌البدل صحبت‌کردیم. احمد رحیمی با اتفاق نظر همه اعضا به‌عنوان دبیر همبند انتخاب شد و نیز تصمیم بر آن شد که محمدامین ولی به اعضای شورای مرکزی اضافه شود.در بقیه جلساتی که در تابستان داشتیم، درباره حوزه فعالیت‌های همبند، تقسیم وظایف میان اعضا و برنامه‌های انجمن برای سال پیش رو صحبت کردیم. دقیق‌تر شرح اتفاقات را می‌توانید در پست قبلی ما دنبال کنید.مدرسه تابستانی علوم ریاضی شریفاولین رویدادی که در دوره جدید با کمک همبند برگزار شد، مدرسه تابستانی علوم ریاضی شریف بود. این برنامه برای دانش‌آموزان پایه‌های یازدهم و دوازدهم دبیرستان و به‌منظور آشنایی آن‌ّها با موضوعاتی از ریاضیات دانشگاهی برپا شده بود. در طول سه روز، دانش‌آموزان در دو سخنرانی (که سخنرانان آن‌ها دکتر شهشهانی و دکتر علیشاهی بودند.) و چهار کارگاه با موضوعات رمزنگاری، توپولوژی، ترسیم‌های هندسی و نظریه‌ بازی‌ها شرکت می‌کردند. تهیه محتوای کارگاه‌ها بر عهده دانشجویان ورودی ۹۷، ۹۶ و ۹۵ بود که با راهنمایی بعضی از اساتید و دانشجو‌های دکتری مطالب هر کارگاه را آماده و اجرا کردند.سخنرانی دکتر شهشهانیکارگاه‌هادر انتهای برنامه نیز پس از آن‌که دانش‌آموزان در یک مسابقه مهیج شرکت کردند، پنلی با هدف بحث درباره ادامه تحصیل در رشته ریاضی داشتیم. مدعوین این پنل آقایان دکتر سلمان ابوالفتح بیگی، علی کمالی‌نژاد، مجید میرزاوزیری، علی رجائی و عرفان صلواتی بودند.پنل بحث درباره‌ی ادامه تحصیل در رشته‌ی ریاضی - از راست به چپ، دکتر عرفان صلواتی، دکتر سلمان ابوالفتح بیگی، دکتر علی کمالی‌نژاد، دکتر مجید میرزاوزیری و دکتر علی رجائیدرایه در راه است...در طول تابستان، بحث‌های زیادی برای به‌راه انداختن مجله ریاضی دانشکده و یک نشریه فرهنگی انجام شد. این بحث‌ها هم درون خود همبند و هم با بعضی از اساتید و دانشجوهای دکتری مطرح شدند.  در نهایت، آن‌چه که فعلا از دستمان برآمده است، ویژه‌نامه‌ای برای ورودی‌هاست تا آن‌ها را با فضای دانشکده و حال‌و‌هوای دانشگاه و زندگی دانشجویی آشنا کند. این ویژه‌نامه تقریبا هر هفته به‌دست ورودی‌ها خواهد رسید.همخوانی‌هاتابستان فرصت مناسبی بود برای دانشجوهایی که می‌خواستند موضوعاتی که در درس‌های معمول دانشگاه چندان به آن‌ها پرداخته نمی‌شود را مطالعه کنند. از همان اول تابستان بازار چند همخوانی در اتاق همبند گرم بود: همخوانی نظریه‌اعداد کاتو، همخوانی کتاب «مقدمه‌ای بر خم‌های جبری» فولتون، همخوانی نظریه اطلاعات و همخوانی آمار بیزی.ضمیمه‌ای بر دروس دانشگاه!یکی از برنامه‌های همبند در سال جاری، سلسله جلساتی تحت عنوان «پیوست» است که قرار است شامل لکچرهایی باشد که توسط دانشجویان  ارائه می‌شود. یکی از کارهای ما در تابستان، هماهنگی با ارائه‌دهندگان بود. در همین راستا با تعدادی از دانشجویان کارشناسی و ارشد و دانشجویان دکتری دانشکده خودمان و IPM صحبت کردیم. در این جلسات  قرار است به موضوعاتی بپردازیم که در یکی از سه‌دسته زیر قرار گیرند:موضوعاتی که در درس‌های معمول دوره لیسانس به آن‌ها پرداخته نمی‌شود.معرفی بعضی درس‌هایی که بعضا در کارشناسی یا مقطع مشترک ارائه می‌شود و دانشجویان از محتوای آن‌ها مطلع نیستند.موضوعاتی که باعث تعمیق مطالب آموخته شده در درس‌های معمول لیسانس شود.فعالیت مشترک با انجمن‌های علمی دیگردر تابستانی که گذشت، در تدارک برنامه‌ای مشترک با انجمن علمی فیزیک و انجمن علمی ریاضی دانشگاه تهران و انجمن علمی ژرفا بودیم. این برنامه که نامش مسابقه «یوریکا» است سعی دارد مهارت ارائه‌ی علم به زبان ساده برای عموم مردم را در شرکت‌کنندگانش تقویت کند.از کارهای دیگری که انجام دادیم، بروشوری بود که درباره فعالیت‌های دانشکده در حوزه سرطان( و برنامه‌هایی که دکتر فروغمند و دکتر رزوان با همکاری همبند در این رابطه برگزار کرده بودند) برای مدرسه تابستانی سرطان آماده کردیم. این مدرسه در تاریخ ۵ تا ۷ شهریورماه توسط گروه میکروبیولوژی دانشگاه تهران برگزار شد.کمی هم تفریح…به‌هر حال تابستان بود و وقت خالی و فرصت برای کمی کارهای غیر علمی:). در جریان برگزاری مدرسه تابستانه، تعدادی بازی فکری به‌دستمان رسید(همین‌جا جا دارد تشکر کنیم از خانم خسروی‌زاده که بازی‌ها را به ما امانت دادند.) و چند هفته‌ای بازار بازی‌ فکری در اتاق گرم بود. البته با اعتراضاتی که از طرف شورای صنفی دانشکده انجام شد و برخی مصلحت‌اندیشی‌ها، فعلا بساطش را جمع کرده‌ایم تا بعد؛).جلسه با شورای صنفیدر واپسین روزهای تابستان، دوره جدید صنفی و همبند باهم جلسه گذاشتند تا درباره نحوه استفاده از اتاق صحبت کنند. بیان آنچه در این جلسه بر ما گذشت خود مجال دیگری می‌طلبد اما امیدواریم این جلسه، شروع خوبی برای فعالیت‌های مشترک و ارتباط این دو نهاد دانشجویی دانشکده باشد.در پایان مثل پست قبلی دوباره می‌گم که طبق اساس‌نامه‌ی انجمن علمی، همه‌ی اعضای دانشکده عضوی از انجمن علمی هستن و شورای مرکزی تنها نقش تصمیم‌گیری برای کمک به فعالیت‌های فوق برنامه‌ای که اعضای دانشکده دوست دارن برگزار کنن رو داره، به همین خاطر ازتون می‌خوام که اگر مسئله‌ای هست که بهش فکر می‌کنید و به نظرتون در چارچوب انجمن ‌علمی می‌تونه قرار بگیره، با بچه‌های شورای مرکزی صحبت کنید، مارو کمک کنید تا بهترین راه رو برای بهترکردن دانشکده‌ی خودمون برداریم، برای ارتباط با انجمن علمی کافیه با هر یک از اعضا صحبت کنید یا به آی‌دی تلگرام @hamband_admin پیام بدین.</description>
                <category>علیرضا توفیقی محمدی</category>
                <author>علیرضا توفیقی محمدی</author>
                <pubDate>Tue, 24 Sep 2019 01:47:19 +0330</pubDate>
            </item>
                    <item>
                <title>شروع دوره‌ی جدید انجمن علمی همبند</title>
                <link>https://virgool.io/hamband/%D8%B4%D8%B1%D9%88%D8%B9-%D8%AF%D9%88%D8%B1%D9%87-%D8%AC%D8%AF%DB%8C%D8%AF-%D8%A7%D9%86%D8%AC%D9%85%D9%86-%D8%B9%D9%84%D9%85%DB%8C-zvwxqptumvzp</link>
                <description>حدود یک‌ماه پیش نتایج انتخابات شورای مرکزی انجمن علمی همبند برگزار شد، انتخاباتی که به شکلی متفاوت از سال‌های پیش و به صورت اینترنتی بود، پس از این انتخابات، اعضای قبلی شورای مرکزی طی جلسه‌ای که در صبح شنبه‌ی ۹ تیر برگزار شد، انجمن علمی را به دوره‌ی جدید واگذار کردند.پس از آن، جلسه‌ی انتخاب دبیر و تصمیم‌گیری بر سر اعضای علل بدل برگزار شد، در این جلسه احمد به عنوان دبیر انجمن علمی توسط همه‌ی اعضای شورای مرکزی انتخاب شد و همچنین محمدامین ولی از اعضای علل بدل، به تصمیم بقیه‌ی اعضای شورای مرکزی به عضویت در شورای مرکزی همبند در آمد.تا الآن، ۳ جلسه هفتگی برگزار کردیم، یکی همین انتقال تجربه و تعیین دبیر بود که در بالا توضیح داده شد؛ در ادامه، مختصری در مورد ۲ جلسه‌ی دیگر و رویکردی که دوست داریم به سمت آن برویم صحبت می‌کنیم.مدیریت خودموناولین چالشی که واسه‌ی برگزای جلسات بهش برخوردیم بحث زمان بود، اینکه بتونیم یه جوری برنامه ریزی کنیم که تا جای ممکن همه بتونن بیان، مخصوصا اینکه دوتامون در اطراف تهرانیم و من شاغلم مسئله رو سخت‌تر می‌کنه، واسه‌ی این کار یک گروه تلگرام بین خودمون زدیم که هماهنگی‌هامون رو اونجا انجام بدیم و نوتیفیکیشنشو باز گذاشتیم که حرف مهم توش بزنیم، واسه‌ی زمان ولی دیدیم فقط گروه و حرف زدن کافی نیست و به doodle.com رو آوردیم، برای مستند کردن صورت جلساتمون، از دانشنامه‌ای که از دوره‌ی قبل برامون به یادگار مونده بود ( https://hamband.math.sharif.edu/wiki/ ) قرار شد استفاده کنیم و حتما مستند کنیم همه‌چی رو که باقیات الصالحات بشه به قولی، برای مدیریت کارهامون هم تصمیم گرفتیم trello بسازیم ولی هنوز استفاده‌ی کاملی ازش نکردیم و باید بیشتر نیازسنجی کنیم براش به نظرم...دومین جلسهبعد از دودل و این چیزا بالاخره زمان جلسه‌ی دوم در اومد و رفتیم اولین جلسه‌ی تصمیم‌گیری شورای مرکزی همبند رو برگزار کنیم.برای انجمن‌علمی‌ای چون همبند که هنوز ساختار منظم و دقیقی برای آن تعیین نشده، تنها وظیفه‌ی مشخصی که هر سال وجود دارد دبیر است، بقیه‌ی وظایف به شکل انعطاف پذیر با توجه به آن دوره از دانشکده و انجمن علمی توسط شورای مرکزی تعیین می‌شود، ما نیز باید چنین کاری می‌کردیم؛ اینکه اصلا مسئولیتی تعریف بشه منوط بر اینه که دغدغه‌ای وجود داشته‌باشه، به همین خاطر مدتی از جلسه رو به بیان مسائلی از دانشکده و خود انجمن علمی که وجود داره یا خوبه به سمت بهتر کردن اوضاع پیش بریم صحبت کردیم و کلی مشکل و مسئله به دست آوردیم، مرحله‌ی بعد برای اینکه بتونیم وظیفه‌ای تعیین کنیم، دسته‌بندی کردن این مشکلات بود که برای این کار به هر مسئله یک یا چند کلید‌واژه نسبت داده و بر اساس اونها، تصمیم گرفتیم که انجمن علمی همبند، در سال ۹۸-۹۹ به این مسائل فکر کنه و سعی کنه در حد توانش و امکاناتش گامی در جهت اونها برداره، بر همین اساس به تعدادی دسته‌بندی از مشکلات رسیدیم که تعدادیش مسائل خودِ همبند و تعدادیش هم مسائل دانشکده و دانشجویان بود.در ادامه‌ی همین جلسه سعی کردیم فعلا وظایف مشخص‌تر رو مشخص کنیم، در همین راستا، نقشی به نام نائب دبیر یا دبیر داخلی تعریف کردیم و علی این نقش رو بر عهده گرفت و همچنین نقشی به نام مسئول امور مالی تعریف شد که مسئولیت‌هاش دراومد و محمد امین اونو بر عهده گرفت.همچنین کمی در مورد زیرساخت‌های فنی، نشاط دانشجویان و نشریاتی که انجمن علمی میتونه داشته‌باشه و مجله ریاضی همفکری کردیم و در مورد ادامه‌ی چیزی مثل درایه و چیزی مثل مجله ریاضی خودمون ولی در حد دانشجوی لیسانس صحبت کردیم ولی جلسه به دلیل کمبود وقت به پایان رسید و بحث‌ها نیمه کاره موند.صورت این جلسه رو می‌تونید از لینک روبرو بخونید: جلسه_بررسی_اهداف_و_تعیین_وظایفجلسه سومجلسه‌ی سوم برای ادامه همین صحبت‌ها بود که بچه‌ها یه مقدار بیشتر در مورد مسائل فکر کرده‌بودند و میخواستیم مسائلی که موندن رو دقیق‌تر بررسی کنیم، اگر نیازه برای خودش مسئولی تعیین کنیم و اگر لازمه تقسیمش کنیم و برای زیربخشاش مسئولی تعیین کنیم که وظیفه‌ی مسئول یک چیز اینه که حواسش باشه کارها توش اون مسئله پیش نیاد یا انجمن علمی کارهایی بکنه که به سمت بهتر شدن در اون موضوع پیش بریم، در این جلسه در مورد موضوع زیرساخت منابع انسانی و مستندات انجمن و اینکه اعضای دانشکده و ما ارتباطاتمون کمه صحبت‌هایی کردیم و من مسئول این شدم که زیرساخت‌هایی رو سعی کنم بهش فکر کنم و در جهت فراهم شدنش پیش برم که این مسائل بهتر بشه.در مورد مسائل آموزشی صحبت کردیم، مسائل آموزشی ماشاالله، از حد گذر می‌کنه صحبت‌هایی کردیم، درمورد اینکه انجمن علمی چه‌کارهایی می‌تونه برای مسائل آموزشی بکنه همفکرایی کردیم ولی مسئولی تعیین نکردیم، چراکه فکرکردن به این مسائل از توان یک نفر خارجه و نیازه هم بیشتر روشون فکر کنیم، هم برای بخش‌های مختلفش با توجه به راهکارهای شورای مرکزی انجمن علمی مسئولیتی تعیین کنیم.در مورد روابط عمومی صحبت کردیم و اینکه چه کار‌هایی می‌تونه روابط عمومی انجام بده، تهش من و علی باهم قرار شد روابط عمومی رو جلو ببریم.در مورد برنامه‌ی ورودی‌ها و اهداف انجمن مخصوص ورودی‌ها هم صحبت کردیم، قرار شد پرهام مراقب کارهای ورودی‌ها باشه و براشون برنامه بچینه.یکی از مسائلی که توی جلسه‌ی قبل صحبت شده بود بحث نشاط دانشجویان بود که بین خودمون بهش گفتیم نسیم، محمد هم قرار شد مسئول فرهنگی و نشاط دانشجویان باشه و یکی از دغدغه‌هاش این باشه که به این مسائل فکر کنه و حواسش باشه کاری هم توی این زمینه انجام بدیم.ولی بازهم زمان جلسه‌مون تموم شد و مجبور شدیم ادامه‌شو بذاریم توی جلسات بعدی.صورت این جلسه رو می‌تونید از لینک روبرو بخونید: بزودیمرداد؟توی مرداد، اولین رویداد رو داریم که از قبل از شروع انجمن، سه‌تا از بچه‌ها دغدغه‌شو داشتن و پیگیری‌هاشو می‌کردن و اون مدرسه تابستانه علوم ریاضی هستش که به امید خدا به خوبی و خوشی برگزار می‌شه.همچنین نقش‌ها و مسئولیت‌هایی که هنوز در موردشون صحبت نکردیم یا کردیم ولی دقیق نبودن رو دقیق‌تر می‌کنیم و برای طول سال تحصیلیمون فکر می‌کنیم.انتهای پیامیکی از چیزهایی که توی دوره‌ی جدید دوست داریم روش تمرکز کنیم، بحث ارتباط با دانشجویانمون هست، طبق اساس‌نامه‌ی انجمن علمی، همه‌ی اعضای دانشکده عضوی از انجمن علمی هستن و شورای مرکزی تنها نقش تصمیم‌گیری برای کمک به فعالیت‌های فوق برنامه‌ای که اعضای دانشکده دوست دارن برگزار کنن رو داره، به همین خاطر ازتون می‌خوام که اگر مسئله‌ای هست که بهش فکر می‌کنید و به نظرتون در چارچوب انجمن ‌علمی می‌تونه قرار بگیره، با بچه‌های شورای مرکزی صحبت کنید، مارو کمک کنید تا بهترین راه رو برای بهترکردن دانشکده‌ی خودمون برداریم.</description>
                <category>علیرضا توفیقی محمدی</category>
                <author>علیرضا توفیقی محمدی</author>
                <pubDate>Fri, 19 Jul 2019 14:46:54 +0430</pubDate>
            </item>
                    <item>
                <title>مشاوره: حذف دبلیو در دانشگاه شریف</title>
                <link>https://virgool.io/@tofighi/%D9%85%D8%B4%D8%A7%D9%88%D8%B1%D9%87-%D8%AD%D8%B0%D9%81-w-aywdh4tk36oo</link>
                <description> خب احتمالا توی کانالا و صفحه‌ی اول edu دیدین که درباره‌ی حذف دبلیو نوشته شده؛ توی این پست قراره یه مقدار درباره قوانین حذف دبلیو و یه مقدار حرف‌های مشاوره‌ای در مورد اینکه اصلا حذف کردن یا نکردن یه مزیت‌ها و معایبی داره صحبت کنیم....حذف دبلیو چیه؟طبق قانون کنونی دانشگاه هر دانشجو حق داره یکی از درس‌هایی رو که برداشته توی تاریخ معینی در نزدیکی پایان کلاس‌ها (که امسال ۲۷ آذر تا ۲ دی‌ هستش) حذف اضطراری کنه.نکته‌ی این حذف اینه که درس به طور کلی از کارنامه‌ت حذف نمی‌شه، بلکه به جای نمره‌ش یه W میاد به این عنوان که این درس رو بعد از اینکه برداشتی حذف کردی و همچنین هر ترم حداکثر یک درس رو می‌تونی حذف کنی و نباید واحدات کمتر از ۱۲ بشه. (دوستایی که با سهمیه‌ی ایثارگران وارد شدن می‌تونن ۲ تا درس رو حذف کنن و حداقل واحد براشون ۱۰ هستش)حذف دبلیو خوبه یا بده؟اگه می‌بینید درسی رو اگه حذف دبلیو نکنید قراره به احتمال بالای ۹۰٪ بیوفتید، حذفش کنید اما بازهم قبلش با یه سال بالایی که یه مقدار عاقل باشه و استاد راهنماتون مشورت بگیرید. (کلا استاد راهنما باید تایید کنه تا حذف دبلیو اتفاق بیوفته)اما قبل از حذف کردن خوبه به نکات زیر توجه کنید:نگاهی به چارت بندازید، ببینید چه درسایی رو با حذف کردن این درس در ترم‌های آینده نمی‌تونید بردارید و عقب میوفتین.نگاهی به ارائه شدن درس بندازیم، مثلا بعضی درس‌ها سالی یکبار ارائه میشن و اگه حذفش کنی یک‌سال دیگه باید برش داری.جوگیر و رویایی تصمیم نگیرید، اگه این ترم دارین مثلا x می‌شید و فکر می‌کنید بهترین حالت و اگه مثلا میان‌ترم رو جوری که فکر می‌کردین داده بودین y میشدین، قرار نیست ترم بعد که درسو برمی‌دارین همون y بشین، کلا دوبار داشتن یک درس یه جور خستگی‌ای توی آدم میاره و حس دروغین بلد بودن درس که چون دفعه دومه داریدش باعث میشه نمره‌ی درس شاید ۸۰٪ اون y هم نشه! (البته حرف قطعی‌ای نیست و کلیه...) کلا حذف دبلیو مثل طلاق می‌مونه، وجود داره ولی فقط برای اضطرار هستش، باید حواستون باشه نباید به خاطر تنبلی یا بهونه‌هایی مثل این برید سراغش، با تمام قدرت زورتون رو بزنید اما منطقی هم تصمیم بگیرید! مراقب باشید جوری نشه که یک حذف دبلیو باعث حذف دبلیوی هر ترمه‌ی شما بشه...مراقب باشید درس‌های کارگاه، گرافیک و تربیت‌بندی رو نمی‌تونید حذف کنید!تایم فرجه اینقد زیاده که اگه حتی ۲۷ واحد درس اختصاصی هم داشته‌باشید، با برنامه‌ریزی مناسب می‌تونید جوری درس بخونید که پایان‌ترم همه‌شو خوب شید، اینکه دوتا درس سخت دارین یهو به این فکر نندازه‌تتون که یکیشون رو حذف کنید که واسه اون یکی وقت بیشتر داشته باشید... این دلیل همون تنبلیه که داره گولتون میزنه، قرار نیست حالا یه درسو حذف کنید واسه اون درس خیلی وقت بیشتری بذارید، به جاش یه کم دو هفته‌ی فرجه رو بیشتر جدی بگیرید، درست تلاش کنید و با برنامه تلاش کنید، هدرش ندین همه‌چی اوکی میشه.درکل حذف دبلیو از افتادن بهتره و اینو بدونید اگه یک درس k واحدی رو بیوفتین و وقتی افتادین x شده باشین و دفعه‌ی بعد y بشین، توی معدل هر دو نمره ثبت میشه (مثل درس 2k واحدی که به طور میانگین (x+y)/2 شده‌باشین) اما معدل فقط در صورتی که مثلا خیلی شدید به تغییر رشته فکر می‌کنید چیز مهمیه واسه ترم یک و دو، وگرنه تاجای ممکن سعی کنید درسو پاس کنید تموم بشه بره.چجوری حذف می‌کنن؟اینجوریه که باید برید توی سایت edu.sharif.edu، بعد از اینکه وارد شدین اون بالا منوهاش برید توی منوی خدمات آموزشی، برید توی منوی درخواست حذف اضطراری خدمات آموزشی -&gt; درخواست حذف اضطراریو بعد سمت چپ درسی که می‌خواید حذف کنید روی درخواست حذف می‌زنید.بعد توضیحات حذف رو می‌نویسید و روی ارسال کلیک می‌کنید. دقت کنید درخواست باید به تایید استاد راهنماتون برسه، بهتره یه ایمیل هم به استاد راهنماتون بزنید بهش خودتون بگید که آقا میخوام حذف کنم، اوکی‌ای دیگه؟ (ایمیل استادا رو هم توی سایت http://sina.sharif.ir/email.php می‌تونید نگاه کنید)مراقب خودتون باشید تصمیمی بگیرید که یه سال دیگه بگید خدایا شکرت که این کارو کردم!همین دیگه تموم شد...و من الله توفیق!</description>
                <category>علیرضا توفیقی محمدی</category>
                <author>علیرضا توفیقی محمدی</author>
                <pubDate>Tue, 18 Dec 2018 23:57:11 +0330</pubDate>
            </item>
            </channel>
</rss>